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
- FE redirect user den:
- GET /api/auth/oauth/google/login?returnUrl=
- Backend build authorize URL va redirect qua Google.
- Google callback ve backend:
- GET /api/auth/oauth/google/callback?code=...&state=...
- 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=.
- FE goi API exchange:
- POST /api/auth/oauth/exchange
- Body: { "code": "..." }
- 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
- Tao OAuth client (Web application).
- Authorized redirect URIs:
- https:///api/auth/oauth/google/callback
- (dev) https://localhost:/api/auth/oauth/google/callback
- 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:
- ResolveProvider(...)
- Doc provider trong OAuthProviders.
- Neu key = azuread ma khong co trong OAuthProviders, fallback AzureAdOptions.
- BuildAuthorizeUrl(...)
- Build URL authorize theo endpoint cua provider.
- Them scope, state, redirect_uri.
- Neu bat hosted-domain check thi them hd=.
- 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).
- ValidateIdTokenAsync(...)
- Lay metadata OIDC (.well-known/openid-configuration).
- Validate issuer, audience, signing key, lifetime.
- 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.
- 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.
- 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
- Login
- GET /api/auth/oauth/{provider}/login?returnUrl=...
- Redirect sang provider authorize URL.
- 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.
- 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)
- Nguoi dung bam nut Google Login:
- Window.location -> GET /api/auth/oauth/google/login?returnUrl=<FE_CALLBACK_URL>
- Sau callback backend, FE nhan code tu query string.
- FE goi:
POST /api/auth/oauth/exchange
Content-Type: application/json
{ "code": "<code-tu-query>" }
- 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
- Loi IdToken not found in token response
- Provider dang khong tra OIDC token.
- Kiem tra scope co openid chua.
- Loi Email not found in token
- Kiem tra claim trong Google token (email, preferred_username).
- Loi Email domain is not allowed
- Check AllowedDomain va claim hd.
- Loi Role 'Pending' not found
- Tao role Pending trong bang Roles.
- 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.