# 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 ```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: ```csharp builder.Services.Configure( builder.Configuration.GetSection(AzureAdOptions.SectionName)); builder.Services.Configure( builder.Configuration.GetSection(OAuthProvidersOptions.SectionName)); builder.Services.AddScoped(); ``` 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. 2) BuildAuthorizeUrl(...) - Build URL authorize theo endpoint cua provider. - Them scope, state, redirect_uri. - Neu bat hosted-domain check thi them hd=. 3) 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). 4) ValidateIdTokenAsync(...) - Lay metadata OIDC (.well-known/openid-configuration). - Validate issuer, audience, signing key, lifetime. 5) 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. 6) 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. 7) 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. 2) 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. 3) Exchange - POST /api/auth/oauth/exchange Request: ```json { "code": "one-time-code" } ``` Response thanh cong: ```json { "token": "", "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: ```sql -- 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" = ''; ``` 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= 2. Sau callback backend, FE nhan code tu query string. 3. FE goi: ```http POST /api/auth/oauth/exchange Content-Type: application/json { "code": "" } ``` 4. 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. 2) Loi Email not found in token - Kiem tra claim trong Google token (email, preferred_username). 3) Loi Email domain is not allowed - Check AllowedDomain va claim hd. 4) Loi Role 'Pending' not found - Tao role Pending trong bang Roles. 5) 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.