Để tích hợp đăng nhập bằng tài khoản GitHub vào ứng dụng ASP.NET Core sử dụng Identity, cần cấu hình đúng các thành phần liên quan đến xác thực, cơ sở dữ liệu và nhà cung cấp OAuth.
Cài đặt gói NuGet cần thiết
Dưới đây là danh sách các gói NuGet bắt buộc:
Microsoft.EntityFrameworkCorevà các gói mở rộng:SqlServer,Tools,Design,ProxiesMicrosoft.AspNetCore.IdentityvàMicrosoft.AspNetCore.Identity.EntityFrameworkCoreAspNet.Security.OAuth.GitHub— thư viện hỗ trợ OAuth với GitHub
Cấu hình GitHub App
Trước tiên, tạo ứng dụng trên GitHub Developer Settings. Sau khi đăng ký thành công, bạn sẽ nhận được Client ID và Client Secret. Lưu hai giá trị này vào tập tin appsettings.json:
{
"Authentication": {
"GitHub": {
"ClientId": "gh_abc123def456",
"ClientSecret": "ghs_xyz789uvw012"
}
}
}
Cấu hình dịch vụ trong Program.cs
Khởi tạo các dịch vụ cần thiết cho Identity, Entity Framework và xác thực bên ngoài:
var builder = WebApplication.CreateBuilder(args);
// Cấu hình kết nối cơ sở dữ liệu
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
.UseLazyLoadingProxies());
// Cấu hình Identity
builder.Services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
// Cấu hình tùy chọn Identity
builder.Services.Configure<IdentityOptions>(options =>
{
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.SignIn.RequireConfirmedEmail = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireDigit = false;
});
// Cấu hình xác thực (Cookie + GitHub)
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.SlidingExpiration = true;
})
.AddGitHub(options =>
{
options.ClientId = builder.Configuration["Authentication:GitHub:ClientId"];
options.ClientSecret = builder.Configuration["Authentication:GitHub:ClientSecret"];
options.Scope.Add("user:email");
});
var app = builder.Build();
Xử lý đăng nhập bên ngoài trong Controller
Controller xử lý luồng đăng nhập gồm các hành động chính: hiển thị form đăng nhập, xử lý đăng nhập nội bộ và tích hợp đăng nhập qua GitHub.
Dưới đây là đoạn mã rút gọn của AccountController với logic xử lý đăng nhập ngoại bộ:
public class AccountController : Controller
{
private readonly SignInManager<AppUser> _signInManager;
private readonly UserManager<AppUser> _userManager;
private readonly ILogger<AccountController> _logger;
public AccountController(
UserManager<AppUser> userManager,
SignInManager<AppUser> signInManager,
ILogger<AccountController> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[HttpGet]
public IActionResult Login(string returnUrl = "/")
{
var model = new LoginViewModel
{
ReturnUrl = returnUrl,
ExternalProviders = _signInManager
.GetExternalAuthenticationSchemesAsync().Result.ToList()
};
return View(model);
}
[HttpPost]
public IActionResult ExternalLogin(string provider, string returnUrl = "/")
{
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), new { returnUrl });
var properties = _signInManager
.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
[HttpGet]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = "/")
{
returnUrl = Url.Content(returnUrl);
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
_logger.LogWarning("Không lấy được thông tin đăng nhập bên ngoài.");
return RedirectToAction(nameof(Login), new { returnUrl });
}
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
if (string.IsNullOrEmpty(email))
{
ViewBag.ErrorMessage = "GitHub không cung cấp địa chỉ email. Vui lòng cập nhật hồ sơ.";
return View("Error");
}
var user = await _userManager.FindByEmailAsync(email);
if (user != null)
{
if (!await _userManager.IsEmailConfirmedAsync(user))
{
ModelState.AddModelError("", "Email chưa được xác minh.");
return View("Login", new LoginViewModel { ReturnUrl = returnUrl });
}
var result = await _signInManager.ExternalLoginSignInAsync(
info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded) return LocalRedirect(returnUrl);
}
// Tạo người dùng mới nếu chưa tồn tại
user ??= new AppUser
{
UserName = email,
Email = email,
EmailConfirmed = true // GitHub email thường đã xác minh
};
var createResult = await _userManager.CreateAsync(user);
if (createResult.Succeeded)
{
await _userManager.AddLoginAsync(user, info);
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
foreach (var error in createResult.Errors)
ModelState.AddModelError("", error.Description);
return View("Login", new LoginViewModel { ReturnUrl = returnUrl });
}
}
Giao diện đăng nhập (Login.cshtml)
View hiển thị nút đăng nhập GitHub cùng các tùy chọn khác:
@model LoginViewModel
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="mb-3">
<label asp-for="Email" class="form-label"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Password" class="form-label"></label>
<input asp-for="Password" type="password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Đăng nhập</button>
</form>
<hr />
<h5>Hoặc đăng nhập bằng:</h5>
<form asp-action="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl">
@foreach (var provider in Model.ExternalProviders)
{
<button type="submit"
name="provider"
value="@provider.Name"
class="btn btn-outline-secondary me-2">
@provider.DisplayName
</button>
}
</form>
Lưu ý triển khai
- Luôn kiểm tra tính hợp lệ của email từ GitHub trước khi tạo người dùng mới.
- Không yêu cầu xác minh email bổ sung nếu GitHub đã xác minh (có thể điều chỉnh tùy theo chính sách).
- Sử dụng
Url.IsLocalUrl()để ngăn chặn open redirect khi xử lý tham sốreturnUrl. - Đảm bảo cấu hình CORS và HTTPS nếu triển khai trên môi trường sản xuất.