Login Flow Contract (SSO)¶
Last updated: May 2026
This document defines the canonical login flow contract for all frontends that integrate with Codevertex SSO (auth-api/auth-ui). It aligns with sso-integration-guide.md and trinity-authorization-pattern.md.
1. Canonical Signatures¶
All frontends should follow the same logical contract. Two patterns exist for historical reasons; preferred is (returnTo first, tenant optional).
Preferred (ordering-frontend, rider-app, cafe-website)¶
// Start login: returnTo = URL to resume after login; tenant = slug when in tenant context (e.g. from path).
redirectToSSO(returnTo?: string, tenant?: string): Promise<void>
// Handle OAuth callback: callbackUrl must match the redirect_uri used in the authorize request.
handleSSOCallback(code: string, callbackUrl: string, tenantSlug?: string): Promise<void>
- Tenant-scoped UIs (ordering, logistics, treasury, pos, inventory): When the user is on a route that includes
orgSlug(e.g./[orgSlug]/dashboard), pass that slug astenant/tenantSlugso the authorize URL includes?tenant=<slug>and the token is minted for that org. Callback URL must match seed (e.g.https://ordering.../acme-retail/auth/callback). - Platform / mixed UIs (notifications, subscriptions): Support both platform-level and tenant-level access. When in tenant context (e.g. route or selection), pass
tenant; when at platform level, omit it. Backend and filters behave per section 3 below.
Variant (treasury-ui, pos-ui, inventory-ui)¶
Some tenant-scoped UIs use orgSlug first (required) because every route is under [orgSlug]:
redirectToSSO(orgSlug: string, returnTo?: string): Promise<void>
handleSSOCallback(orgSlug: string, code: string, callbackUrl: string): Promise<void>
Behavior is the same: callback URL is origin/${orgSlug}/auth/callback, and the authorize URL must include tenant=<orgSlug> so the token is minted for that tenant. These UIs should still pass tenant into buildAuthorizeUrl (either as param or via orgSlug).
2. Service Summary Table¶
| Service | Client ID | Callback path | redirectToSSO | handleSSOCallback | Notes |
|---|---|---|---|---|---|
| ordering-frontend | ordering-ui | /{tenant}/auth/callback |
(returnTo?, tenant?) |
(code, callbackUrl, tenantSlug?) |
Canonical; tenant from path. |
| treasury-ui | treasury-ui | /{orgSlug}/auth/callback |
(orgSlug, returnTo?) |
(orgSlug, code, callbackUrl) |
Tenant required in route. |
| pos-ui | pos-ui | /{orgSlug}/auth/callback |
(orgSlug, returnTo?) |
(orgSlug, code, callbackUrl) |
Same as treasury. |
| inventory-ui | inventory-ui | /{orgSlug}/auth/callback |
(orgSlug, returnTo?) |
(orgSlug, code, callbackUrl) |
Same as treasury. |
| logistics-ui | logistics-ui | /{orgSlug}/auth/callback |
(returnTo?, tenant?) |
(code, callbackUrl, tenantSlug?) |
Aligned to preferred; tenant from path when available. |
| notifications-ui | notifications-ui | /auth/callback or /{tenant}/auth/callback |
(returnTo?, tenant?) |
(code, callbackUrl) |
Platform + tenant; optional tenant. |
| subscriptions-ui | subscriptions-ui | /auth/callback or /{tenant}/auth/callback |
(returnTo?, tenant?) |
(code, callbackUrl) |
Platform + tenant; optional tenant. |
| rider-app | rider-app | /auth/callback |
(returnTo?, tenant?) |
(code, callbackUrl) |
Tenant optional for join flow. |
| cafe-website | cafe-website | /auth/callback |
(returnTo?) |
(code, callbackUrl) |
No tenant in path. |
| auth-ui | auth-ui | N/A (login UI) | N/A | N/A | Users log in here; no redirectToSSO. |
Production hosts and redirect URI patterns are in sso-integration-guide.md and auth-api/cmd/seed/main.go.
3. Platform vs Tenant Scope (All Services)¶
Backends are built to support both platform scope and tenant scope. Frontends must behave consistently.
Platform users (e.g. platform owner, superuser)¶
- Headers: Do not send
X-Tenant-IDorX-Tenant-Slug(or send only when user explicitly selects a tenant for the request). - UI: Show tenant dropdown / tenant filter on list pages and data tables so the user can scope data to a tenant. Use a shared, centralized tenant-select component where applicable so logic is controlled from one place.
- Login: May land on platform-level routes (e.g.
/dashboard) or tenant routes; passtenantonly when the user is in a tenant context (e.g. path contains orgSlug).
Tenant users (single-tenant org members)¶
- Headers: Send
X-Tenant-IDand optionallyX-Tenant-Slugon every request to tenant-scoped backends (from profile/me or first successful login). - UI: Do not show tenant dropdown / tenant filter; all data is already scoped to that tenant. Backend filters by tenant from headers.
- Login: When on a tenant route (e.g.
/{orgSlug}/...), passtenant/orgSlugso the token is minted for that org.
Notifications-ui and Subscriptions-ui (core, both scopes)¶
- These are core services that other services integrate with (like auth-ui/auth-api). They are not only tenant-scoped in the frontend: they support both platform and tenant access.
- Routes: May be platform-level (e.g.
/dashboard) or tenant-level (e.g./{orgSlug}/...) if such routes exist. Seed allows both/auth/callbackand/{tenant}/auth/callback. - Login flow: Same as others: when in tenant context, pass
tenanttoredirectToSSOand use tenant in callback path if applicable; when at platform level, omit tenant. Authorize URL and token minting follow the same rules. - Headers: Platform users do not send tenant by default (tenant filters shown); tenant users send tenant in headers (no tenant filter).
- Backend: Already supports both scopes; frontend only needs to pass or omit tenant and show/hide tenant filters as above.
4. Build Authorize URL¶
- Authorize URL:
GET https://sso.codevertexafrica.com/api/v1/authorizewith PKCE (code_challenge,code_challenge_method=S256),client_id,redirect_uri,state, and optionaltenant. - When the frontend has a tenant context (e.g. from path or selection), pass
tenantso auth-api mints the token for that org. When at platform level or no tenant context, omittenant; auth-api may resolve from user’s primary org for direct login from auth-ui. redirect_urimust match exactly one of the client’s redirect URIs in the seed (production domains in sso-integration-guide).
5. Callback and After Login¶
- Callback page: read
code(andstate) from query, callhandleSSOCallback(code, callbackUrl).callbackUrlmust be the same asredirect_uriused in the authorize step. - After tokens are stored: fetch profile (SSO
GET /api/v1/auth/meor service’s/auth/mewith fallback to SSO), then redirect toreturnTo(e.g. fromsessionStorage) or default destination. - Persist
tenant_idandtenant_slugfrom profile when present so headers and UI can follow platform vs tenant rules above.
6. Tenant Select Filter (Recommendation)¶
- Use a shared, centralized tenant-select filter component in each frontend that supports platform scope. Use it on list pages and data tables where tenant filtering applies.
- Platform users: Component visible; selection drives
X-Tenant-ID/X-Tenant-Slugfor the request (or scope in URL). - Tenant users: Component hidden or not rendered; tenant is fixed from auth context and headers.
This keeps “show tenant filter vs not” and “send tenant headers vs not” consistent and maintainable across services.
7. Embedded SSO via SSOLoginModal (Iframe)¶
For services that want to trigger login without a full-page redirect to accounts.codevertexafrica.com, the @bengo-hub/shared-ui-lib provides SSOLoginModal — a React component that embeds auth-ui in a modal iframe.
import { SSOLoginModal } from "@bengo-hub/shared-ui-lib/auth";
<SSOLoginModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onLoginSuccess={(result) => {
// result.accessToken, result.refreshToken, result.user
handleLoginSuccess(result);
}}
onLoginFailed={(err) => handleError(err)}
/>
postMessage events (from iframe → parent):
- auth:login_success — login succeeded; includes token payload
- auth:login_failed — login failed
- auth:resize — iframe height changed (handled automatically)
The modal auto-closes after a successful login. Tenant context can be passed as a prop to pre-scope the auth-ui login form.
Current version: @bengo-hub/shared-ui-lib v0.1.5
Note: auth-ui and treasury-ui are the providers of iframe content — they do not use
SSOLoginModalorTreasuryPaymentModalthemselves. These components are for consumer services.