TTMT.ManageWebGUI/SSO-OAuth-OIDC.md
2026-04-07 13:20:46 +07:00

9.5 KiB

Google OAuth (OIDC) + AzureAD Shared SSO Service

Tai lieu nay tong hop toan bo flow dang nhap OAuth/OIDC theo muc coding cho codebase hien tai.

Pham vi:

  • Giu nguyen login username/password cu.
  • Dung chung 1 service cho AzureAD SSO va Google OAuth OIDC.
  • Van tao/cap nhat user noi bo trong DB.
  • User moi vao role Pending (khong co permission), doi admin cap quyen.

1) Trang thai implementation hien tai

Da hoan tat trong code:

  • Shared service theo provider key: SsoService.
  • Controller da provider: OAuthController.
  • Controller AzureAD cu (SsoController) da chay tren shared service.
  • Config da provider qua OAuthProviders trong appsettings.

File chinh:

  • TTMT.CompManageWeb/Program.cs
  • TTMT.CompManageWeb/Interfaces/ISsoService.cs
  • TTMT.CompManageWeb/Services/SsoSerivce.cs
  • TTMT.CompManageWeb/Controllers/OAuthController.cs
  • TTMT.CompManageWeb/Controllers/SsoController.cs
  • TTMT.CompManageWeb/Dtos/Auth/OAuthProviderOptions.cs
  • TTMT.CompManageWeb/Dtos/Auth/OAuthProvidersOptions.cs
  • TTMT.CompManageWeb/appsettings.json

2) Kien truc luong dang nhap

2.1 Luong OAuth Google

  1. FE redirect user den:
    • GET /api/auth/oauth/google/login?returnUrl=
  2. Backend build authorize URL va redirect qua Google.
  3. Google callback ve backend:
    • GET /api/auth/oauth/google/callback?code=...&state=...
  4. Backend:
    • Exchange code -> token endpoint.
    • Validate id_token.
    • Lay email/name.
    • Kiem tra domain.
    • Upsert user noi bo.
    • Issue JWT noi bo.
    • Tao one-time code (2 phut).
    • Redirect ve returnUrl?code=.
  5. FE goi API exchange:
    • POST /api/auth/oauth/exchange
    • Body: { "code": "..." }
  6. Backend consume one-time code va tra payload login (token + role + permission).

2.2 Luong AzureAD cu

Van giu endpoint cu:

  • GET /api/auth/sso/login
  • GET /api/auth/sso/callback
  • POST /api/auth/sso/exchange

Nhung ben trong da dung chung service theo provider azuread.


3) Cau hinh bat buoc

3.1 appsettings.json

{
  "OAuthProviders": {
    "DefaultProvider": "azuread",
    "Providers": {
      "google": {
        "Authority": "https://accounts.google.com",
        "AuthorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
        "TokenEndpoint": "https://oauth2.googleapis.com/token",
        "ClientId": "",
        "ClientSecret": "",
        "CallbackPath": "/api/auth/oauth/google/callback",
        "AllowedDomain": "",
        "PendingRoleName": "Pending",
        "Scopes": "openid profile email",
        "Issuer": "https://accounts.google.com",
        "EmailClaim": "email",
        "NameClaim": "name",
        "HostedDomainClaimName": "hd",
        "EnforceHostedDomainClaim": true
      }
    }
  }
}

Giai thich nhanh:

  • AllowedDomain:
    • Rong: cho phep moi domain.
    • Co gia tri (vd hust.edu.vn): chi cho email domain nay.
  • EnforceHostedDomainClaim=true:
    • Neu co claim hd trong token thi bat buoc phai khop AllowedDomain.
    • Neu token khong co hd, he thong fallback check theo email domain.
  • PendingRoleName: role duoc gan khi user moi dang nhap lan dau.

3.2 Google Cloud Console

  1. Tao OAuth client (Web application).
  2. Authorized redirect URIs:
    • https:///api/auth/oauth/google/callback
    • (dev) https://localhost:/api/auth/oauth/google/callback
  3. Lay ClientId, ClientSecret va dien vao appsettings (hoac secrets).

4) Dang ky DI va options

Trong Program.cs can co:

builder.Services.Configure<AzureAdOptions>(
    builder.Configuration.GetSection(AzureAdOptions.SectionName));

builder.Services.Configure<OAuthProvidersOptions>(
    builder.Configuration.GetSection(OAuthProvidersOptions.SectionName));

builder.Services.AddScoped<ISsoService, SsoService>();

OAuthProvidersOptions dung dictionary provider theo key (vd google, azuread, okta).


5) Thiet ke interface va service chung

5.1 Interface ISsoService

Interface da duoc mo rong thanh provider-aware:

  • BuildAuthorizeUrl(provider, redirectUri, state)
  • ExchangeCodeAsync(provider, code, redirectUri, ct)
  • ValidateIdTokenAsync(provider, idToken, ct)
  • IsAllowedDomain(provider, email, principal)
  • UpsertUserAsync(provider, email, name, ct)

Va van giu overload cu de backward compatibility.

5.2 SsoService - logic tong

Core y tuong:

  • Resolve config theo provider key.
  • Provider nao khong co endpoint explicit thi suy ra tu metadata/authority.
  • AzureAD duoc fallback tu AzureAdOptions de khong vo luong cu.

Phan quan trong:

  1. ResolveProvider(...)
  • Doc provider trong OAuthProviders.
  • Neu key = azuread ma khong co trong OAuthProviders, fallback AzureAdOptions.
  1. BuildAuthorizeUrl(...)
  • Build URL authorize theo endpoint cua provider.
  • Them scope, state, redirect_uri.
  • Neu bat hosted-domain check thi them hd=.
  1. ExchangeCodeAsync(...)
  • Goi token endpoint voi client_id, client_secret, code, redirect_uri.
  • Parse OidcTokenResponse.
  • Bat buoc co id_token (flow hien tai dang OIDC-centric).
  1. ValidateIdTokenAsync(...)
  • Lay metadata OIDC (.well-known/openid-configuration).
  • Validate issuer, audience, signing key, lifetime.
  1. IsAllowedDomain(...)
  • Neu AllowedDomain rong -> cho phep.
  • Neu khong rong -> check duoi email.
  • Neu EnforceHostedDomainClaim bat va token co claim hd -> bat buoc hd trung AllowedDomain.
  1. UpsertUserAsync(...)
  • Tim user theo email (UserName).
  • Neu ton tai: cap nhat ten + metadata update.
  • Neu chua ton tai: tao user moi, Password = null, gan role PendingRoleName.
  1. One-time code
  • CreateOneTimeCodeAsync(...): tao code, luu bang SsoOneTimeCodes, het han sau 2 phut.
  • ExchangeOneTimeCodeForLoginAsync(...): consume code, tra payload login final.

6) Controller va contract API

6.1 OAuthController (da provider)

Route base: api/auth/oauth

  1. Login
  • GET /api/auth/oauth/{provider}/login?returnUrl=...
  • Redirect sang provider authorize URL.
  1. Callback
  • GET /api/auth/oauth/{provider}/callback?code=...&state=...
  • Xu ly token + domain + upsert + issue token + one-time code.
  • Redirect ve FE voi query code.
  1. Exchange
  • POST /api/auth/oauth/exchange

Request:

{
  "code": "one-time-code"
}

Response thanh cong:

{
  "token": "<jwt-noi-bo>",
  "name": "...",
  "username": "email@domain",
  "access": [1, 2, 3],
  "role": {
    "roleName": "Pending",
    "priority": 99
  }
}

6.2 SsoController (AzureAD legacy)

Van giu route cu, nhung da goi service chung voi provider azuread.


7) Rule user va role Pending

Rule bat buoc:

  • User OAuth lan dau phai duoc tao trong bang UserAccounts.
  • User moi phai vao role Pending.
  • Role Pending khong co permission nao (PermissionRoles.IsChecked = 0 hoac khong co row).
  • Admin se cap role/permission sau.

Model lien quan:

  • UserAccounts
  • Roles
  • PermissionRoles
  • SsoOneTimeCodes

Kiem tra nhanh trong DB:

-- 1) Kiem tra role Pending co ton tai
SELECT Id, RoleName, Priority
FROM "Roles"
WHERE "RoleName" = 'Pending';

-- 2) Kiem tra Pending role khong co permission active
SELECT pr.*
FROM "PermissionRoles" pr
JOIN "Roles" r ON r."Id" = pr."RoleId"
WHERE r."RoleName" = 'Pending'
  AND pr."IsChecked" = 1;

-- 3) Kiem tra user tao boi OAuth
SELECT "Id", "UserName", "Password", "RoleId", "CreatedBy", "UpdatedBy"
FROM "UserAccounts"
WHERE "UserName" = '<email-user>';

Ky vong:

  • Query #2 tra ve 0 rows.
  • User moi co Password = null, CreatedBy = 'SSO'.

8) Frontend integration (chi tiet)

  1. Nguoi dung bam nut Google Login:
    • Window.location -> GET /api/auth/oauth/google/login?returnUrl=<FE_CALLBACK_URL>
  2. Sau callback backend, FE nhan code tu query string.
  3. FE goi:
POST /api/auth/oauth/exchange
Content-Type: application/json

{ "code": "<code-tu-query>" }
  1. FE luu token va xu ly permission/role giong login cu.

9) Luu y bao mat va hardening

Nen lam tiep:

  • Validate returnUrl theo allowlist de tranh open redirect.
  • Luu va verify state/nonce server-side (cache/redis).
  • Dung IHttpClientFactory thay new HttpClient() de kiem soat timeout/retry.
  • Dua ClientSecret sang secret manager/env var.
  • Bat HTTPS va secure cookie policy day du.

10) Troubleshooting

  1. Loi IdToken not found in token response
  • Provider dang khong tra OIDC token.
  • Kiem tra scope co openid chua.
  1. Loi Email not found in token
  • Kiem tra claim trong Google token (email, preferred_username).
  1. Loi Email domain is not allowed
  • Check AllowedDomain va claim hd.
  1. Loi Role 'Pending' not found
  • Tao role Pending trong bang Roles.
  1. Loi exchange code het han
  • One-time code chi song 2 phut va chi dung 1 lan.

11) Checklist test E2E truoc khi merge

  • Login Google thanh cong voi account hop le.
  • User moi duoc tao trong DB va role = Pending.
  • Pending role khong co permission active.
  • User cu login lai thi khong tao duplicate user.
  • One-time code chi dung 1 lan.
  • AllowedDomain rong cho phep tat ca domain.
  • AllowedDomain co gia tri thi chan dung domain.

12) Ghi chu pham vi hien tai

Hien tai flow moi dang OIDC-centric (bat buoc id_token). Neu can support OAuth thuan (khong co id_token), can bo sung nhanh:

  • UserInfo endpoint call.
  • Mapping email/name tu userinfo response.
  • Branch logic trong callback de fallback userinfo.