Bilko Mobile Phase 0 Auth Bridge — Status 2026-06-05
Bilko
null
Update 0 Auth Bridge2026-07-10 — StatusT1 2026-06-05
Executive summary
Bilko mobile direction is native iPhone + Samsung/Android, with React Native + Expo as the implementation path. The PWA/mobile-web direction is superseded for the companion mobile app.
Phase 0 backend/auth work is implemented locally and targeted tests are green. TheCIAM mobile app buildprovisioning shouldCOMPLETE not(MC be dispatched until the remaining gates below are closed.
What was changed#105193)
Documentation and architecture
Updated or created:
docs/mobile/MOBILE-ARCHITECTURE.mdCanonical docs/mobile/MOBILE-IMPL-SPEC-PHASE1.mddocs/mobile/MOBILE-PRD.mddocs/mobile/README.mddocs/INDEX.md/Users/makinja/system/specs/bilko-tech-stack.mddocs/architecture/ADR-037-BILKO-MOBILE-NATIVE-ENTRA-AUTH.mddocs/backend/MOBILE-ENTRA-AUTH-BRIDGE-SPEC.md
Key doc decisions:
Native iOS/Androidmobile appisregistration:the916bb9f3-658d-4729-b5a0-64b1f157c8c2target,"BilkonotMobilePWA.(Native)" React—Nativesingle-tenant (AzureADMyOrg), public client, PKCE, redirectscom.alai.bilko://auth+Expo is the chosen native cross-platform path.Entra External ID + OIDC Authorization Code + PKCE is the target mobile/customer login model.Phase 1 uses existingmsauth.com.alai.bilko:/api/v1/*endpoints; a dedicated/mobile/*BFF is deferred.Mobile refresh must not depend on browser cookies.Email claims from Entra are not trusted for login mapping.
Backend/auth implementation
Added or changed:
apps/api/src/main/kotlin/no/alai/bilko/auth/EntraExternalIdService.ktVerifies Microsoft Entra External ID JWTs.Fails closed if issuer/audience/JWKS config is missing.Enforces RS256 and.kidVerifiesStrayconfiguredduplicateissuer31d2eb67and(invalidaudience.AzureADandPersonalMicrosoftAccountaudience for CIAM, no API perms) deleted 2026-07-10.ExtractsAdmin consent verifiedsubject(Graphfromoauth2PermissionGrants,): bilko-apisubAllPrincipalsaccess_as_user+ Graphopenid profile offline_access email User.Read.apps/mobile/eas.json: client ID wired in all 3 build profiles;EXPO_PUBLIC_API_URLon Azure stage FQDN. PR 90 squash-merged to main (1cd39b59);ENTRA_SCOPESnow includesapi://fe39e0f5-513e-40af-93f0-c3ee624df56c/access_as_user,withusePKCE: true.- Securion (Parisa) S3: PASS-WITH-FINDINGS; F1 open → live decoded access token must carry
oidfallback.+
P2Papps/api/src/main/resources/db/migration/V64__entra_external_identities.sql- peer
Addspre-verify (Proveo native agent): PASS 7/7. Evidence:entra_external_identitiesmapping table.Maps verifiedissuer + subjectto existing Bilko user.Adds SECURITY DEFINER functions with fixedSET search_path = public, pg_temp:bilko_auth.find_user_by_entra_identity(text, text)bilko_auth.mark_entra_login(text, text)
apps/api/src/main/kotlin/no/alai/bilko/db/AuthUserRepository.ktAdds.findByEntraIdentity(issuer, subject)~/system/evidence/105193/AddsLive probes 2026-07-10: stage API= 200; CIAM OIDC discovery = 200 (authorize/token endpoints resolve).markEntraLogin(issuer,GETsubject)/api/v1/health
apps/api/src/main/kotlin/no/alai/bilko/auth/AuthService.ktAddscreateSessionFromEntraIdToken(idToken).Maps verified Entra identity to active Bilko user/org/role.Issues existing Bilko access + refresh tokens.
apps/api/src/main/kotlin/no/alai/bilko/plugins/DI.ktWiresEntraExternalIdServiceintoAuthService.
apps/api/src/main/kotlin/no/alai/bilko/routes/AuthRoutes.ktAddsPOST /api/v1/auth/entra/session.AddsPOST /api/v1/auth/mobile/refresh.Preserves existing web cookie refresh endpointPOST /api/v1/auth/refresh.Hardened bad-body handling by removingprintStackTrace()and detailed parser error echo from register bad-body response.
Tests added or extended
apps/api/src/test/kotlin/no/alai/bilko/auth/EntraExternalIdServiceTest.ktvalid tokenwrong audiencewrong issuerexpired tokenHS256 rejectionmissingsub/oidmissing config fail-closed
apps/api/src/test/kotlin/no/alai/bilko/db/AuthUserRepositoryTest.ktFlyway migration execution on disposable PostgreSQL/Testcontainers DB, including V64.mapped Entra identity returns userunmapped identity returns nullinactive mapped user returns nullsoft-deleted mapped user returns nullorg/role/status assertionsmarkEntraLogin()metadata update
apps/api/src/test/kotlin/no/alai/bilko/auth/AuthServiceTest.ktrefresh-token rotation/reuse rejection test
apps/api/src/test/kotlin/no/alai/bilko/routes/AuthRoutesHttpIntegrationTest.kt/auth/entra/sessionmissingidTokenreturns 400/auth/entra/sessionmissing Entra config returns 503CONFIGURATION_ERROR/auth/mobile/refreshmissingrefreshTokenreturns 400/auth/mobile/refreshwith structurally valid but stale/non-DB refresh JTI returns 401web cookie refresh/logout stale-token regression remains covered
Validation evidence
Evidence files:
/Users/makinja/system/evidence/bilko-mobile-doc-review-20260604.md/Users/makinja/system/evidence/bilko-mobile-auth-local-security-audit-20260604.md/Users/makinja/system/evidence/securion-review-check-102962-20260605.md
Commands recorded as green in evidence:
./gradlew compileKotlin --no-daemon→ BUILD SUCCESSFUL./gradlew compileKotlin compileTestKotlin --no-daemon→ BUILD SUCCESSFUL./gradlew test --tests no.alai.bilko.auth.EntraExternalIdServiceTest --no-daemon→ BUILD SUCCESSFUL./gradlew integrationTest --tests no.alai.bilko.db.AuthUserRepositoryTest --no-daemon→ BUILD SUCCESSFUL./gradlew integrationTest --tests no.alai.bilko.auth.AuthServiceTest --no-daemon→ BUILD SUCCESSFUL./gradlew integrationTest --tests no.alai.bilko.routes.AuthRoutesHttpIntegrationTest --no-daemon→ BUILD SUCCESSFULCombined targeted run:./gradlew compileKotlin compileTestKotlin test --tests no.alai.bilko.auth.EntraExternalIdServiceTest integrationTest --tests no.alai.bilko.db.AuthUserRepositoryTest --tests no.alai.bilko.auth.AuthServiceTest --tests no.alai.bilko.routes.AuthRoutesHttpIntegrationTest --no-daemon→ BUILD SUCCESSFUL
Where we stand
Done locally
Mobile architecture direction cleaned up and made native-first.Entra External ID bridge implemented.Mobile-safe body refresh endpoint implemented.V64 migration implemented and executed via Testcontainers/Flyway integration test.Targeted local tests passed.Local security audit completed with no critical/high local findings; one route error-handling hygiene issue was fixed.
Still blocked
No independent Securion/QA domain PASS has been received.MC#102962was checked and is still open in/Users/makinja/system/evidence/securion-review-check-102962-20260605.md.Real Entra staging trigger substitutions are provisioned/verified, but the current Azure tenant isAAD(alemalai.onmicrosoft.com), not a separate CIAM customer tenant. Customer-facing CIAM tenant policy/MFA/passwordless configuration remains a later production gate.No live environment/browser/mobile-device verification has been performed for this auth bridge.
Decision
Do not dispatch mobile app build yet.
Next concrete step is to stop waiting for passive task pickup and run/obtain a real independent security/QA review, then provision/test real Entra External ID config. After those pass, dispatch the native React Native + Expo mobile build.
Stage deploy — substitution wiring (MC #102996, 2026-06-05)
infrastructure/gcp/cloudbuild-stage.yaml is now substitution-ready for Entra External ID metadata:
Three substitutions added to thesubstitutions:block:_ENTRA_EXTERNAL_ID_ISSUER,_ENTRA_EXTERNAL_ID_AUDIENCE,_ENTRA_EXTERNAL_ID_JWKS_URL, all defaulting to__UNSET__.A conditional block in thedeploy-api-no-trafficstep buildsENTRA_ENV_VARS. If all three substitutions are non-__UNSET__and non-empty, they are appended to--set-env-varsas;ENTRA_EXTERNAL_ID_ISSUER=...;ENTRA_EXTERNAL_ID_AUDIENCE=...;ENTRA_EXTERNAL_ID_JWKS_URL=.... OtherwiseENTRA_ENV_VARS=""— the env-vars string is unchanged and current stage behaviour is preserved exactly.Values are provisioned in thebilko-stage-auto-deploytrigger as of 2026-06-05:_ENTRA_EXTERNAL_ID_ISSUER=https://login.microsoftonline.com/3454a03f-20b4-4bda-a116-2293c459aecd/v2.0_ENTRA_EXTERNAL_ID_AUDIENCE=95b2a55f-8f48-4c9c-b4f8-eb455e3bdfd7_ENTRA_EXTERNAL_ID_JWKS_URL=https://login.microsoftonline.com/3454a03f-20b4-4bda-a116-2293c459aecd/discovery/v2.0/keys
No stage smoke has been run against Entra yet. Stage will only receive these env vars after the committed Cloud Build wiring is pushed/merged and the trigger deploys a new API revision.
Immediate next actions
Force active review path for MC#102962or run a direct independent Securion/QA validation with evidence.Push/merge the clean Phase 0 branch through the gated path.Trigger a stage build and confirmMC#102996: Entra metadata presentlog line appears and the three vars are visible in the Cloud Run revision env.Run/api/v1/auth/entra/sessionagainst a real Entra test tenant token.Run/api/v1/auth/mobile/refreshwith real issued mobile refresh token.Close Securion MC#102989with passing evidence.Only after PASS evidence, dispatch native mobile implementation.