Tích hợp Đăng nhập GitHub vào ASP.NET Core Identity

Để 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.EntityFrameworkCore và các gói mở rộng: SqlServer, Tools, Design, Proxies
  • Microsoft.AspNetCore.IdentityMicrosoft.AspNetCore.Identity.EntityFrameworkCore
  • AspNet.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 IDClient 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.

Thẻ: aspnetcore Identity github-oauth entityframeworkcore authentication

Đăng vào ngày 8 tháng 7 lúc 16:25