Auth Adapter Stack (Code Level)
C4 Level 4 detail of the PSI authentication system. Shows the layered adapter architecture, plugin system, and OIDC provider implementations.
Class Hierarchy
classDiagram
class AuthAdapter {
<<interface>>
+getSession(request) Promise~Session~
+getUser(userId) Promise~User~
+getUserByEmail(email) Promise~User~
+createUser(data) Promise~User~
+deleteUser(userId) Promise~void~
+createCustomToken(userId) Promise~string~
+listUsers() Promise~User[]~
+importUsers(users) Promise~void~
}
class BetterAuthAdapter {
#betterAuth: BetterAuth
#dbAdapter: DatabaseAdapter
+getSession(request)
+getUser(userId)
+getUserByEmail(email)
+createUser(data)
+deleteUser(userId)
#initBetterAuth(plugins)
}
class FirebaseAuthAdapter {
-firebaseAdmin: FirebaseAdmin
+createCustomToken(userId)
+listUsers()
+importUsers(users)
#initBetterAuth(plugins)
}
AuthAdapter <|.. BetterAuthAdapter : implements
BetterAuthAdapter <|-- FirebaseAuthAdapter : extends
class BetterAuthPlugin {
<<interface>>
+id: string
+init(ctx): void
}
class FirebaseIdTokenPlugin {
+id: "firebase-id-token"
+init(ctx)
-verifyIdToken(token) Promise~DecodedToken~
}
class GenericOidcPlugin {
+id: "generic-oidc"
+init(ctx)
-verifyJwt(token, jwksUri) Promise~JwtPayload~
-fetchJwks(uri) Promise~JWKS~
}
BetterAuthAdapter --> BetterAuthPlugin : uses plugins
BetterAuthPlugin <|.. FirebaseIdTokenPlugin
BetterAuthPlugin <|.. GenericOidcPlugin Token Verification Chain
sequenceDiagram
participant Client as PSI Frontend
participant Router as Hono Router
participant AuthAdapter as FirebaseAuthAdapter
participant BetterAuth as better-auth Core
participant FBPlugin as firebaseIdToken Plugin
participant OIDCPlugin as genericOidc Plugin
participant FBAdmin as Firebase Admin SDK
participant IdP as OIDC Provider JWKS
Client->>Router: Request with Authorization: Bearer <token>
Router->>AuthAdapter: getSession(request)
AuthAdapter->>BetterAuth: Process session request
BetterAuth->>FBPlugin: Try verify as Firebase ID token
FBPlugin->>FBAdmin: verifyIdToken(token)
alt Firebase ID token valid
FBAdmin-->>FBPlugin: DecodedToken {uid, email, ...}
FBPlugin-->>BetterAuth: Session {user, isValid: true}
else Not a Firebase token
FBAdmin-->>FBPlugin: Error: invalid token
FBPlugin-->>BetterAuth: Pass to next plugin
BetterAuth->>OIDCPlugin: Try verify as OIDC JWT
OIDCPlugin->>IdP: GET /.well-known/jwks.json
IdP-->>OIDCPlugin: JWKS with public keys
OIDCPlugin->>OIDCPlugin: Verify JWT signature + claims (via jose)
alt OIDC JWT valid
OIDCPlugin-->>BetterAuth: JwtPayload {sub, email, iss, ...}
else Invalid JWT
OIDCPlugin-->>BetterAuth: Authentication failed
end
end
BetterAuth-->>AuthAdapter: Session or error
AuthAdapter-->>Router: User identity or 401 SSO Token Conversion (Full Detail)
sequenceDiagram
participant User
participant Frontend as PSI Frontend
participant IdP as OIDC Provider
participant Backend as PSI Backend
participant AuthAdapter as FirebaseAuthAdapter
participant OIDC as genericOidc Plugin
participant FBAuth as Firebase Admin
participant JWKS as Provider JWKS Endpoint
User->>Frontend: Click SSO login button
Frontend->>IdP: Redirect to authorization endpoint
Note over IdP: client_id, redirect_uri,<br/>scope=openid email profile,<br/>response_type=id_token (fragment)<br/>or response_type=code (code mode)
IdP->>User: Login form
User->>IdP: Credentials
IdP->>Frontend: Redirect with id_token (fragment) or code
alt Code mode (Radio-Canada)
Frontend->>Backend: POST /api/auth/convertToken {code}
Backend->>IdP: Exchange code for tokens
IdP-->>Backend: {id_token, access_token}
else Fragment mode (ZDF, CBC, RTBF, etc.)
Frontend->>Backend: POST /api/auth/convertToken {jwt: id_token}
end
Backend->>AuthAdapter: convertToken(jwt)
AuthAdapter->>OIDC: Verify JWT
OIDC->>JWKS: Fetch public keys
JWKS-->>OIDC: JWKS
OIDC->>OIDC: Verify signature, iss, aud, exp
AuthAdapter->>FBAuth: getOrCreateUser(email, displayName)
FBAuth-->>AuthAdapter: Firebase user record
AuthAdapter->>FBAuth: createCustomToken(uid)
FBAuth-->>AuthAdapter: Custom token
Backend-->>Frontend: {loginToken: customToken}
Frontend->>FBAuth: signInWithCustomToken(loginToken)
FBAuth-->>Frontend: Firebase session (ID token)
Note over Frontend: All subsequent API calls use<br/>Firebase ID token in Authorization header OIDC Provider Configurations
graph LR
subgraph "genericOidc Plugin"
Verify[JWT Verification]
end
subgraph "Provider Implementations"
ZDF[ZDF Keycloak<br/>psi-login.zdf.de<br/>login.zdf.digital]
CBC[CBC Azure AD B2C<br/>login.cbc.radio-canada.ca]
RC[Radio-Canada Azure AD B2C<br/>login.cbc.radio-canada.ca]
RTBF[RTBF Login<br/>login.rtbf.be]
NPO[NPO ID<br/>npoid-sandbox.npoid-dev.nl]
SRG[SRG SSR Account<br/>account-int.srgssr.ch]
end
subgraph "Provider Helpers"
AzureHelper[azure-ad-b2c.ts<br/>B2C tenant config]
CBCHelper[cbc-rc.ts<br/>CBC/RC-specific config]
end
Verify --> ZDF
Verify --> CBC
Verify --> RC
Verify --> RTBF
Verify --> NPO
Verify --> SRG
CBC --> AzureHelper
RC --> AzureHelper
AzureHelper --> CBCHelper Client-Side Auth
classDiagram
class ClientAuthAdapter {
<<interface>>
+getCurrentUser() User | null
+getIdTokenAsync() Promise~string~
+signOutAsync() Promise~void~
+signInWithGoogleAsync() Promise~void~
+signInWithTokenAsync(token: string) Promise~void~
+onAuthStateChanged(callback) Unsubscribe
}
class FirebaseAuthAdapter {
-auth: FirebaseAuth
+getCurrentUser()
+getIdTokenAsync()
+signOutAsync()
+signInWithGoogleAsync()
+signInWithTokenAsync(token)
+onAuthStateChanged(callback)
}
class NoopAdapter {
+getCurrentUser() null
+getIdTokenAsync() ""
+signOutAsync()
+signInWithGoogleAsync()
+signInWithTokenAsync()
+onAuthStateChanged()
}
ClientAuthAdapter <|.. FirebaseAuthAdapter : default
ClientAuthAdapter <|.. NoopAdapter : testing
note for FirebaseAuthAdapter "Wraps Firebase Auth SDK.\nSupports Google popup sign-in\nand custom token sign-in (SSO)." Admin Authorization
graph TD
A[API Request] --> B{Is adminFunction?}
B -->|No| C[Execute publicFunction]
B -->|Yes| D[Check admin role]
D --> E[Read: silo/siloKey/module/admin/userRoles/email]
E --> F{Role exists?}
F -->|Yes: Owner| G[Execute adminFunction]
F -->|No| H[Return 403 Forbidden] Further Reading
- Authentication Flow -- C4 Level 3 sequence diagrams
- Adapter Pattern -- all adapter interfaces
- SSO Integration Guide (psi-product) -- adding new providers