Initial Guide — Entra External ID (CIAM): จาก tenant เปล่า ถึง state ปัจจุบัน
จุดประสงค์: หลัง provision tenant Microsoft Entra External ID แล้ว (หน้า App registrations ยังว่าง) ต้อง config อะไรต่อ เพื่อให้ SuperApp (shell, Sentinel Gateway, UserService, NotificationService, admin portal) login / sign-up / ออก token ได้เหมือน state ปัจจุบัน ตรวจสอบกับของจริงเมื่อ: 2026-09-25
- CIAM tenant:
EXIM BANK Customer NonProd=eximcustnonprd.onmicrosoft.com=e18959dc-d97f-426c-b40e-09799337e28f(tenantType: CIAM) — authorityhttps://eximcustnonprd.ciamlogin.com/- Workforce tenant (admin portal / พนักงาน):
exim.go.th=6ff02f6c-cf97-4a42-9cf9-e27d79c35d3aอ่านคู่กับ initial-guide-apim.md และ initial-guide-aks.md
การอ่าน tag ท้าย bullet:
[live]— query จาก Microsoft Graph ของ tenant จริงวันที่ 2026-09-25[repo]— อ่านจาก source ในSuperApp/*(Backend_UserService, Backend_SentinelGatewayService, Backend_NotificationService, Backend_Iac, Frontend_HostAppSuperApp, Frontend_AdminSuperApp)[assume]— ยังไม่ได้ verify ต้องเช็กเองก่อนใช้
⚠️ ข้อจำกัดของข้อมูล
- ตรวจด้วย
az login --tenant e18959dc…(accountsilak@exim.go.th= guest + Global Administrator ใน CIAM tenant) — first-party app ของ az CLI ไม่มี delegated scope สำหรับ endpoint ต่อไปนี้ → ได้403→ เนื้อหาที่เกี่ยวข้องเป็น[assume]ทั้งหมด
policies/authenticationMethodsPolicy(Email OTP เปิดอยู่ไหม)identity/identityProviders(มี social IdP ไหม)identity/userFlowAttributes(มี custom attribute ไหม)policies/authenticationFlowsPolicy- ทางปิด gap: app
Microsoft Graph Command Line Tools(14d82eec…) มี admin consent ใน tenant นี้อยู่แล้ว[live]→ ใช้ PowerShell:Connect-MgGraph -TenantId e18959dc-d97f-426c-b40e-09799337e28f -Scopes Policy.Read.All,IdentityProvider.Read.All,IdentityUserFlow.Read.All- ไม่มี IaC ที่ provision tenant / app registration ของ dev และ uat — มีแค่
Backend_Iac/scripts/create-sit-app-registration.ps1(clone จาก dev → SIT)[repo]→ ค่าทั้งหมดใน §2–§8 มาจาก tenant ที่รันอยู่ ไม่ใช่ source of truth- Prod tenant
eximcustprdไม่ได้ตรวจ (มีแค่ในenvironments.prod.ts)- login ผ่าน BFF (
bff/login-otp/initiate|verifyใน shell) — หา implementation ไม่เจอใน repo ที่ clone ไว้ → path นี้ unverified
0. ขอบเขต
- ครอบคลุม: หลัง tenant External ID ถูกสร้างแล้ว → app registration → custom authentication extension → user flow → bind → wiring config ฝั่ง app → smoke test
- ไม่ครอบคลุม: การสร้าง tenant เอง (Azure subscription link / billing), custom domain, prod
- CIAM tenant เดียวรับทั้ง DEV / SIT / UAT — แยก environment ด้วย app registration + user flow + custom extension คนละชุด ไม่ได้แยก tenant
[live](คล้าย APIM ที่แยกด้วย displayName prefix) - Workforce tenant ใช้ app ตัวเดียว (
6b0a2469…) ร่วมทุก env[live][repo] - LINE login ไม่ใช่ Entra IdP — verify LIFF idToken ตรงกับ
https://api.line.me/oauth2/v2.1/verify(ChannelId dev2009742852) ไม่ต้อง config ใน Entra[repo] - Role/permission ของ app ไม่ได้มาจาก Entra — มาจาก DB ผ่าน Redis (
RoleClaimType="CustomRoles") → ไม่ต้องสร้าง app role / group ใน Entra[repo]
1. Prerequisite นอก Entra (ขาดอันใดอันหนึ่ง = login / sign-up ตาย)
[!IMPORTANT] ต้องครบทั้ง 4 ข้อก่อนเริ่ม §4 — ขาดข้อใดข้อหนึ่ง login / sign-up ของ env นั้นใช้ไม่ได้
สิทธิ์คนทำ = Global Administrator ของ CIAM tenant
- ถ้าไม่ได้ Global Admin ต้องมีครบ 3 role: Application Administrator + External ID User Flow Administrator + Authentication Extensibility Administrator
- ปัจจุบันมี Global Admin 7 คน: guest
*_exim.go.th#EXT#6 คน +o-parichat@eximcustnonprd.onmicrosoft.com[live]Endpoint ของ custom extension ต้องเข้าได้จาก internet และ TLS ต้อง valid
- Entra เรียก endpoint เหล่านี้จาก cloud ของ Microsoft ไม่ได้เรียกจากใน VNet
- ผลทดสอบ
POSTจากเครื่อง corp วันที่ 2026-09-25 (TLS verify ผ่านทุกตัว)[live]:
Env Endpoint ผล dev …/notification-api/…/auth-extension/email-otp-send✅ 200 uat …/uat-notification-api/…/auth-extension/email-otp-send✅ 200 sit …/sit-notification-api/…/auth-extension/email-otp-send❌ 404 sit …/sit-userservice-api/api/user-service/v1/token-issuance/on-token-issuance-start❌ 404
- ยังไม่ได้ทดสอบจาก internet นอก corp
[assume]- controller ของ endpoint ทั้งหมดในตารางเป็น
[AllowAnonymous]: app ไม่ได้ validate token ที่ Entra ส่งมาเอง[repo]Service ต้องรันอยู่ก่อน bind extension
- NotificationService: ส่ง OTP
- UserService: token issuance, native auth sign-up, Graph
- Sentinel Gateway: OIDC redirect
- ถ้า NotificationService ล่ม Entra จะส่ง OTP ไม่ได้ ทำให้ sign-up / login ด้วย OTP ใช้ไม่ได้ทั้ง env
[assume]Key Vault
sua-azure-nonprd-kv+ CSI SecretProviderClass ต้องมี client secret ของแต่ละ env (ชื่อ secret ดูใน §9)[repo]
- config ใน cloud ตั้ง
KeyVault.VaultUri: ""จึงไม่ได้ใช้ Key Vault provider ใน app; secret เข้ามาเป็น env var ผ่าน CSI เท่านั้น- ถ้าเปิด Key Vault provider ใน app ชื่อ secret แบบ
Dev--TenantRegistry--…จะ map ไปผิด key[repo]
2. ค่าปัจจุบัน (ใช้เป็น target ตอน config)
2.1 Tenant-level
| หัวข้อ | ค่าจริง | tag |
|---|---|---|
| ⚠️ Domain | ⚠️ eximcustnonprd.onmicrosoft.com ตัวเดียว (ไม่มี custom domain) |
[live] |
| Conditional Access | ไม่มี policy | [live] |
| Company branding | default layout, header/footer ซ่อน, hideAccountResetCredentials: true |
[live] |
| Email OTP authentication method | ต้องเปิด (user flow ทุกตัวใช้ EmailOtpSignup-OAUTH) |
[assume] — อ่าน policy ไม่ได้ |
| Social IdP (Google/Facebook) | ไม่ได้ใช้ใน user flow | [live] (ระดับ flow) |
[!WARNING] Tenant นี้ไม่มี custom domain: ทุกอย่างผูกกับ
eximcustnonprd.onmicrosoft.com/eximcustnonprd.ciamlogin.com
- หน้า login / OTP ที่ลูกค้าเห็นจะเป็น URL
eximcustnonprd.ciamlogin.comไม่ใช่โดเมนของ EXIM[live]- ชื่อนี้ hardcode อยู่ใน code / config หลายจุด
[repo]:
knownAuthoritiesในmsal-instance.tsInstance/Domainใน appsettings ของ UserService- path ของ native auth API (
…/eximcustnonprd.onmicrosoft.com/…)authorityใน environments ของ shell- ถ้าจะเพิ่ม custom URL domain ภายหลัง (เช่น
login.exim.go.th) ต้องทำ 2 อย่าง:
- ตั้งค่าฝั่ง Entra: verify domain + ตั้ง Azure Front Door
[assume]- แก้ค่าในทุกจุดข้างบนพร้อมกัน
- tenant prod (
eximcustprd) ควรตัดสินใจเรื่อง custom domain ก่อน go-live[assume]
2.2 ชุด app ต่อ environment
| Env | Customer app (clientId) | Client secret (ชื่อ / หมดอายุ) | User flow | OnOtpSend extension | OnTokenIssuanceStart | AuthExtension-API app |
|---|---|---|---|---|---|---|
| DEV | IDP_Super_APP dba195d4-84a6-43b8-bbc4-fc03b0c068e8 |
UserService-Local-Dev-v2 / 2027-04-07 |
SuperAPP_Flow |
Super APP Custom Email OTP |
ไม่มี | SuperApp-AuthExtension-API 34948d2a… |
| SIT | SuperApp-SIT-Customer-CIAM-V2 a4cef28a-2198-4e72-bed7-92e5e00f2dbf |
SIT-ClientSecret-2026 / 2028-04-27 |
SIT_SuperAPP_Flow_V2 |
SIT-EmailOtpSend |
SIT-TokenIssuanceStart |
SuperApp-AuthExtension-API 1e91c637… |
| UAT | SuperApp-UAT-Customer-CIAM cf5521c5-3fbf-4f76-b047-1af5bafe51cc |
UAT-ClientSecret / 2028-07-14 |
User_Flow_V2 |
UAT-EmailOtpSend |
ไม่มี | SuperApp-UAT-AuthExtension-API 836f359a… |
- clientId ที่ code ใช้: dev
dba195d4/ sita4cef28a/ uatcf5521c5— ตรงกับตารางบน[repo](UserServiceappsettings.{Development,Sit,Uat}.json, SentinelConfig/tenants{,.Sit,.Uat}.json) SuperApp-SIT-Customer-CIAM0d4b7c4a…(V1) ยังมี flow + listener ผูกอยู่แต่ code ไม่ได้ชี้แล้ว → legacy (ดู §11)[live][repo]- Workforce:
SuperApp6b0a2469-1877-4dac-b858-94fc2863fe77ใน tenant6ff02f6c…, secret2 years secret/ 2028-05-13[live]
3. ขั้นตอน config (ตามลำดับที่ต้องทำ)
ลำดับสำคัญ: app → AuthExtension-API app → custom extension → user flow → bind → wiring → test ทำซ้ำ §4–§7 หนึ่งรอบต่อ env (dev / sit / uat) — ใช้
Backend_Iac/scripts/create-sit-app-registration.ps1เป็น reference สำหรับ clone ได้ แต่ ตรวจค่ากับ §4–§7 นี้ เพราะ script ต่างจาก live บางจุด (เช่นresourceIdของ extension)[repo]
4. Customer app registration (1 ตัวต่อ env)
Portal: Entra admin center → Applications → App registrations → New registration
- Supported account types: Accounts in this organizational directory only (
signInAudience: AzureADMyOrg)[live] - Expose an API
[live]- Application ID URI =
api://{clientId} - เพิ่ม scope
access_as_user(admins and users) - Authorized client applications: เพิ่ม clientId ของตัวเองเป็น pre-authorized สำหรับ
access_as_user(dev/sit-V2/uat มีแล้ว)[live] - Manifest:
api.requestedAccessTokenVersion = 2[live] - UserService รับ audience ทั้ง
{clientId}และapi://{clientId}; ไม่ checkscpแต่ต้องขอ scopeaccess_as_userเพื่อให้ได้ audience ถูกตัว[repo]
- Application ID URI =
- Authentication → Web platform (Sentinel OIDC, confidential client,
response_type=code)[live][repo]- Redirect URI:
https://gateway-dev.exim.go.th/{prefix}/sentinel/signin-oidc-customer - Redirect URI:
https://gateway-dev.exim.go.th/{prefix}/sentinel/signout-callback-oidc-customer {prefix}=sentinel-gateway-api(dev) /sit-sentinel-gateway-api(sit) /uat-sentinel-gateway-api(uat)- ปิด implicit grant ทั้ง access token และ ID token
[live]
- Redirect URI:
- Authentication → Single-page application (MSAL ของ shell)
[live][repo]https://superapp-{env}.exim.go.th,…/home,…/login/msal,…/auth/callback(+https://localhost:4200/...สำหรับ dev/sit)- MSAL ใน shell ปัจจุบัน แทบไม่ได้ใช้ (มีแค่
handleRedirectPromiseตอน boot, ไม่มี UI เรียกloginRedirect) — login จริงผ่าน Sentinel (/sentinel/login?tenant=customer) และ BFF OTP[repo] - Post-logout: Sentinel เรียก
/oauth2/v2.0/logoutพร้อมid_token_hintแล้วเด้งกลับ origin ของ SPA → origin ต้องอยู่ใน redirect URI[repo]
- Authentication → Settings
[live][repo]- Allow public client flows = Yes (
isFallbackPublicClient: true) - Enable native authentication = Yes (
nativeAuthenticationApisEnabled: all) — UserService เรียก native auth API ที่https://eximcustnonprd.ciamlogin.com/eximcustnonprd.onmicrosoft.com/…- sign-up:
/signup/v1.0/start(challenge_type=oob redirect) →/signup/v1.0/challenge→/signup/v1.0/continue(grant_type=oob) →/oauth2/v2.0/token(grant_type=continuation_token) - sign-up ไม่ส่ง
client_secret(native auth ปฏิเสธ confidential client →AADSTS550022)[repo]
- sign-up:
- Allow public client flows = Yes (
- Certificates & secrets → New client secret (2 ปี)
[live]- ใช้ 2 งาน: Sentinel แลก code/refresh token + UserService ขอ Graph token แบบ
client_credentials[repo] - เก็บลง Key Vault ตามชื่อใน §9 — ห้ามเก็บใน appsettings / repo
- ใช้ 2 งาน: Sentinel แลก code/refresh token + UserService ขอ Graph token แบบ
- API permissions → Microsoft Graph
[live]- Delegated:
openid,profile,email,offline_access,User.Read(offline_accessจำเป็น — Sentinel refresh token ด้วยgrant_type=refresh_token)[repo] - Application:
User.Read.All,User.ReadWrite.All,UserAuthenticationMethod.ReadWrite.All,Domain.Read.All - กด Grant admin consent — ทั้ง 4 customer app มี consent แล้ว
[live] - Graph call ที่ code ใช้:
GET /users?$filter=identities/any(...),GET /users?$filter=mail eq …,POST /users,PATCH /users/{id}(accountEnabled),GET /organization?$select=verifiedDomains,GET /domains,POST /users/{oid}/revokeSignInSessions(Sentinel ตอน logout)[repo] - ไม่ต้อง เพิ่ม
Azure Resource Manager user_impersonationและ app roleaccess_as_application— มีใน live แต่ code ไม่ใช้ (leftover)[live][repo]
- Delegated:
- Manifest:
api.acceptMappedClaims = true— จำเป็นสำหรับ env ที่ใช้ custom claimUserProfile(§6.2) — dev/sit-V1/sit-V2 =true, uat = null[live] - Token configuration (optional claims) — Sentinel อ่าน
email(หรือpreferred_username),oid,name,given_name,family_nameจาก ID token[repo]- live: มีแค่ app legacy
0d4b7c4aที่ตั้ง optional claimoid; app ที่ใช้งานจริงไม่มี optional claim เลย[live] - ถ้าเจอ ID token ไม่มี
email→ เพิ่ม optional claimemail,given_name,family_nameให้ ID token[assume]
- live: มีแค่ app legacy
- Enterprise application (service principal) ต้องถูกสร้าง (portal สร้างให้อัตโนมัติ),
appRoleAssignmentRequired: false[live]
5. AuthExtension-API app (1 ตัวต่อ env) — ตัวตนที่ Entra ใช้ยิงเข้า custom extension
Portal: เกิดอัตโนมัติตอนสร้าง custom authentication extension (§6) แล้วเลือก "Create new app registration" หรือสร้างเองก่อน
- ชื่อ live:
SuperApp-AuthExtension-API(dev34948d2a…, sit1e91c637…),SuperApp-UAT-AuthExtension-API(uat836f359a…)[live] - Application ID URI =
api://gateway-dev.exim.go.th/{appId}(host ต้องตรงกับ host ของ target URL)[live] - API permissions → Microsoft Graph → Application:
CustomAuthenticationExtension.Receive.Payload+ Grant admin consent — มีครบทั้ง 3 ตัว[live] - ไม่มี secret / redirect URI
[live]
6. Custom authentication extensions
Portal: External Identities → Custom authentication extensions → Create a custom extension
6.1 OnOtpSend (ส่ง OTP ผ่าน NotificationService แทน email ของ Microsoft) — ทุก env
| Env | Display name | Target URL | Auth resource |
|---|---|---|---|
| dev | Super APP Custom Email OTP |
https://gateway-dev.exim.go.th/notification-api/api/notification-service/v1/auth-extension/email-otp-send |
api://gateway-dev.exim.go.th/34948d2a… |
| sit | SIT-EmailOtpSend |
https://gateway-dev.exim.go.th/sit-notification-api/api/notification-service/v1/auth-extension/email-otp-send |
api://gateway-dev.exim.go.th/1e91c637… |
| uat | UAT-EmailOtpSend |
https://gateway-dev.exim.go.th/uat-notification-api/api/notification-service/v1/auth-extension/email-otp-send |
api://gateway-dev.exim.go.th/836f359a… |
- Event type: EmailOtpSend, authentication =
azureAdTokenAuthentication[live] - endpoint ตอบ
microsoft.graph.OtpSend.continueWithDefaultBehavior[repo]
6.2 OnTokenIssuanceStart (custom claim UserProfile) — มีแค่ SIT
- Display name
SIT-TokenIssuanceStart, targethttps://gateway-dev.exim.go.th/sit-userservice-api/api/user-service/v1/token-issuance/on-token-issuance-start, auth resourceapi://gateway-dev.exim.go.th/1e91c637…[live]- หมายเหตุ: script
create-sit-app-registration.ps1ตั้งresourceId = api://{clientId}— live ใช้ AuthExtension-API app ไม่ใช่ customer app → ยึด live[live][repo]
- หมายเหตุ: script
- Claims:
UserProfile(claimsForTokenConfiguration: [{claimIdInApiResponse: "UserProfile"}])[live] - endpoint คืน
provideClaimsForToken→UserProfile= JSON{userId, apps}เข้ารหัส AES ถ้ามีEncryptedClaim__EncryptionKey(map ไว้แค่ SIT; dev/uat จะส่ง JSON ตรง ๆ)[repo] - ต้องทำต่อ 2 จุดบน customer app ของ env นั้น:
- Enterprise applications → app → Single sign-on → Attributes & Claims → Configure custom claims provider → เลือก
SIT-TokenIssuanceStart - เพิ่ม claim
UserProfilesource = AttributecustomClaimsProvider.UserProfile(live SIT-V2 มีclaimsPolicy=UserProfile←customclaimsprovider)[live] acceptMappedClaims = trueใน manifest (§4)[live]
- Enterprise applications → app → Single sign-on → Attributes & Claims → Configure custom claims provider → เลือก
7. User flow (1 ตัวต่อ env)
Portal: External Identities → User flows → New user flow
- Type: sign up and sign in (
externalUsersSelfServiceSignUpEventsFlow), sign-up allowed,userTypeToCreate: member[live] - Identity provider: Email one-time passcode (
EmailOtpSignup-OAUTH) อย่างเดียว[live] - Attributes ที่เก็บ (มีแค่
emailเป็น required)[live]- dev
SuperAPP_Flow: email, city, country, displayName, givenName, surname - sit
SIT_SuperAPP_Flow_V2/ uatUser_Flow_V2: เพิ่ม username, streetAddress, state, postalCode, jobTitle - ห้ามตั้ง attribute อื่นเป็น required — code ส่งแค่
displayName(= email) ตอนattributes_required→ required attribute อื่นทำให้ native sign-up fail[repo] - ชื่อ user flow ไม่ถูกอ้างถึงใน code — ตั้งชื่ออะไรก็ได้
[repo]
- dev
- Applications → Add application → เลือก customer app ของ env นั้น (1 flow ต่อ 1 app)
[live] - Bind extension (ผูกระดับ app ผ่าน authentication event listener)
[live]onEmailOtpSend→ ทุก customer app (dev/sit-V1/sit-V2/uat) ผูกกับ OtpSend ของ env ตัวเองonTokenIssuanceStart→ เฉพาะ sit-V1/sit-V2- ถ้า portal ไม่มีช่อง bind OTP listener ให้สร้างผ่าน Graph
POST /beta/identity/authenticationEventListeners(@odata.type: #microsoft.graph.onEmailOtpSendListener,conditions.applications.includeApplications[].appId,handler.customExtension.id)[assume]
8. Workforce app (SuperApp 6b0a2469… ใน tenant exim.go.th) — สำหรับ admin portal
- Admin portal ไม่มี MSAL clientId ของตัวเอง — login ด้วย
loginSentinelRedirect('employee')→{gateway}/{env-}sentinel-gateway-api/sentinel/login?tenant=employee[repo] - Expose API:
api://6b0a2469-1877-4dac-b858-94fc2863fe77+ scopeaccess_as_user[live] - Web redirect URI (ทุก env):
https://gateway-dev.exim.go.th/{prefix}/sentinel/signin-oidc-employee+…/signout-callback-oidc-employee[live]- มี
…-oidc-customerของ dev/sit/uat ติดมาด้วย (ไม่จำเป็นสำหรับ workforce)[live]
- มี
- SPA redirect:
https://admin-superapp{,-sit,-uat}.exim.go.th/home+ localhost[live] - API permissions → Graph Delegated เท่านั้น:
openid profile offline_access User.Read Directory.Read.All Group.Read.All GroupMember.Read.All User.Read.All(ไม่มีemail)[live]- admin consent ครอบคลุมแค่
Directory.Read.All Group.Read.All GroupMember.Read.All User.Read.All;openid profile offline_access User.Readอาศัย user consent[live] - Sentinel อ่าน
emailถ้าไม่มีใช้preferred_username→ token workforce ยังใช้ได้[repo]; ถ้าต้องการ claimemailให้เพิ่ม scopeemail/ optional claim[assume]
- admin consent ครอบคลุมแค่
- ไม่มี Graph application permission เลย แต่ Sentinel เรียก
POST /users/{oid}/revokeSignInSessionsแบบ app-only ตอน logout ในทั้ง 2 tenant → ใน workforce น่าจะ403(ต้องการUser.RevokeSessions.All)[live][repo]→ ดู §11 - Client secret
2 years secret/ 2028-05-13 → KV{Env}--TenantRegistry--Tenants--employee--AzureAd--ClientSecret[live][repo]
9. Wiring ฝั่ง app (หลัง Entra พร้อม)
- UserService —
AzureAd+TenantRegistryในBackend_UserService/src/UserService01.API/appsettings.{Development,Sit,Uat}.jsonและ override ในBackend_Iac/config/user-service/{dev,sit,uat}/appsettings.json(overlay ตอน build ผ่านpipelines/templates/overlay-cloud-config.yml)[repo]AzureAd.ClientId,Audience = api://{clientId},WorkforceClientId = 6b0a2469…AllowedTenants: CIAMInstance https://eximcustnonprd.ciamlogin.com/+ workforcehttps://login.microsoftonline.com/TenantRegistry.Tenants.customer.AzureAd:Instance,Domain = eximcustnonprd.onmicrosoft.com,TenantId,ClientId,Scopes(openid profile email offline_access api://{clientId}/access_as_user)
- Sentinel Gateway —
Backend_SentinelGatewayService/src/SentinelGateway01.API/Config/tenants{,.Sit,.Uat}.json(tenant keycustomer+employee; sit/uat override แค่customer) +PublicBaseUrl/PathPrefix=/sentinelใน appsettings[repo] - Shell —
Frontend_HostAppSuperApp/apps/shell/src/environments/environments*.ts→msal.clientId,authority,scopes;knownAuthoritieshardcodeeximcustnonprd.ciamlogin.comในmsal-instance.ts[repo] - Key Vault secret (ผ่าน CSI → env var)
[repo]
| Env | KV secret | env var |
|---|---|---|
| dev | Dev--TenantRegistry--Tenants--customer--AzureAd--ClientSecret, Dev--TenantRegistry--Tenants--employee--AzureAd--ClientSecret |
TenantRegistry__Tenants__{customer,employee}__AzureAd__ClientSecret |
| sit | Sit--TenantRegistry--Tenants--{customer,employee}--AzureAd--ClientSecret |
เหมือนกัน |
| uat | Uat--TenantRegistry--Tenants--{customer,employee}--AzureAd--ClientSecret |
เหมือนกัน |
- SecretProviderClass:
Backend_Iac/src/yamls/{dev,sit,uat}/superapp/secret-providers/{user,sentinel-gateway}-secret-provider.yaml(repoIaC/เป็น copy เก่า อย่าใช้)[repo] - SIT เพิ่ม
sit--user-service--Encryption--Key→EncryptedClaim__EncryptionKey(key เข้ารหัสUserProfile)[repo] - Local dev:
dotnet user-secrets set "TenantRegistry:Tenants:customer:AzureAd:ClientSecret" "<secret>"[repo] - หมุน secret = สร้าง secret ใหม่ใน app → อัปเดต KV → restart pod (CSI sync) → ลบ secret เก่า
[assume]
10. Smoke test
- OIDC metadata ต้องตอบ 200 และ
issuer=https://e18959dc-d97f-426c-b40e-09799337e28f.ciamlogin.com/e18959dc-d97f-426c-b40e-09799337e28f/v2.0(ยืนยันแล้ว[live]— ตรงกับ issuer แบบ{tid}.ciamlogin.comที่ UserService รับ)curl -s https://eximcustnonprd.ciamlogin.com/e18959dc-d97f-426c-b40e-09799337e28f/v2.0/.well-known/openid-configuration - Customer login (browser):
https://gateway-dev.exim.go.th/{prefix}/sentinel/login?tenant=customer→ หน้า Entra ขอ email → ได้ OTP ทาง email ที่ส่งจาก NotificationService (ถ้าได้ email ของ Microsoft = OTP listener ไม่ทำงาน) → กลับมาที่signin-oidc-customer - Admin login:
…/sentinel/login?tenant=employee - SIT: decode access token (jwt.ms) ต้องมี claim
UserProfile - ตรวจ config ผ่าน Graph (ใช้ az config แยก ไม่ทับ login หลัก):
export AZURE_CONFIG_DIR=~/.azure-ciam
az login --tenant e18959dc-d97f-426c-b40e-09799337e28f --allow-no-subscriptions
az rest --url "https://graph.microsoft.com/v1.0/applications?\$select=displayName,appId,isFallbackPublicClient,nativeAuthenticationApisEnabled"
az rest --url "https://graph.microsoft.com/beta/identity/authenticationEventsFlows"
az rest --url "https://graph.microsoft.com/beta/identity/customAuthenticationExtensions"
az rest --url "https://graph.microsoft.com/beta/identity/authenticationEventListeners"
11. สิ่งที่พบ / ควรเก็บกวาด (บันทึกไว้ ยังไม่ได้แก้)
- 🔴
SuperApp-Backend(2fc065f0…) secret หมดอายุ 2026-09-26 — ไม่พบ code ที่ใช้ clientId นี้ → น่าจะ legacy; ยืนยันก่อนลบ app[live][repo] - 🔴 Workforce app ไม่มี Graph application permission →
revokeSignInSessionsตอน logout ของ admin น่าจะ fail (ถ้าต้องการ ให้เพิ่มUser.RevokeSessions.All+ admin consent ใน tenantexim.go.th)[live][repo] - 🔴 Target ของ extension SIT ตอบ 404 (
sit-notification-apiOTP send +sit-userservice-apitoken issuance) → ถ้า SIT ยังใช้อยู่ sign-up/login OTP + ออก token ของ SIT น่าจะ fail; ถ้า SIT ถูกปลดแล้ว (APIM guide ระบุว่า "เคยมี SIT") ควรถอด listener/extension/app ของ SIT ออก[live] - 🟡 DEV และ UAT ไม่มี
OnTokenIssuanceStartขณะที่ code มี endpoint และคาดหวัง claimUserProfile— UAT ยังขาดacceptMappedClaims+ custom claims provider ด้วย[live][repo] - 🟡 ไม่มี Graph
Organization.Read.Allแต่ UserService เรียกGET /organization?$select=verifiedDomains→ fallback issuer{tid}.ciamlogin.comไม่ match identity ของ local account (*.onmicrosoft.com) → lookup แรก degrade แบบเงียบ[live][repo] - 🟡 Shell
environments.uat.ts:clientId: ''และ scope ยังชี้api://dba195d4…(dev) — ไม่พังเพราะ MSAL ยังไม่ถูกใช้[repo] - 🟡 Tenant key ไม่ตรง: UserService ใช้
workforce, Sentinel + KV alias ใช้employee→ workforce secret ไม่ bind ใน UserService (ยังไม่พังเพราะ US ใช้แค่ tenant customer)[repo] - 🟡
knownAuthoritieshardcodeeximcustnonprd.ciamlogin.com→ prod (eximcustprd) จะพัง[repo] - ⚪ App legacy/ทดลองที่ไม่อยู่ใน code:
QuickStart Application×2 (77e47da9,7c3c57b6),SuperApp-Bff(c0cba44f),SuperApp-SIT-Customer-CIAMV1 (0d4b7c4a— ยังมี flowSIT_SuperAPP_Flow+ listener ผูก),SuperApp-AuthExtension-API96154fad(ไม่มี permission, hostazurecontainerapps.io)[live][repo] - ⚪ Redirect URI cruft บน DEV app: Azure Container Apps (
bff-service.orangesea-…), Front Door (exim-superapp-…azurefd.net),sa-onboarding-demo.pages.dev,api.sua-az-dev.internal, SPA ของ SIT/UAT — ควรเหลือเฉพาะของ dev + localhost[live] - ⚪ DEV app มี secret ตัวเดียว (
UserService-Local-Dev-v2) ใช้ทั้ง local และ cloud → แยก secret local/cloud จะหมุนง่ายกว่า[live] - ⚪
b2c-extensions-app— ห้ามแก้/ลบ (Entra ใช้เก็บ extension attribute)[live]