Web UI Sign-In
The Fire Arrow Server administration web UI signs users in with OpenID Connect. Sign-in runs through a Backend-for-Frontend (BFF) that lives inside the server: the server itself is the OAuth client, and the browser never receives an access or refresh token.
Starting with Fire Arrow Server 1.14.0, every web UI login profile must be a confidential OAuth client with a client secret. A profile without a client-secret is rejected at startup. If you are upgrading from 1.13.0 or earlier, follow Upgrading from the browser-based login before deploying.
How Sign-In Works
The BFF implements the pattern the IETF best-practice guidance for browser-based apps recommends for applications that handle personal data. The browser holds only a session cookie; all tokens stay on the server.
Because tokens are held server-side:
- The browser cannot leak an access or refresh token — there are none in
localStorageor JavaScript memory. - Token refresh happens on the server, transparently to the browser.
- Signing out revokes the tokens at the identity provider, not just in the browser.
Login Profiles
The login screen can offer several profiles, letting users choose how they authenticate — for example a Patient profile and an Administrator profile that point at different identity providers. Profiles are configured under fire-arrow.admin.web-ui-auth.profiles:
fire-arrow:
admin:
web-ui-auth:
profiles:
- profile: patient-login
name: Patient
authentication-system: azure-b2c
client-id: "${OIDC_PATIENT_CLIENT_ID}"
client-secret: "${OIDC_PATIENT_CLIENT_SECRET}"
scopes: "openid profile"
- profile: admin-login
name: Administrator
authentication-system: azure-services
client-id: "${AZURE_CLIENT_ID}"
client-secret: "${AZURE_CLIENT_SECRET}"
scopes: "openid profile"
client-authentication-method: client_secret_basic
post-logout-redirect-uri: "https://fhir.example.com/admin/login"
| Property | Required | Default | Description |
|---|---|---|---|
profile | Yes | -- | Unique identifier for the profile. Also used as the OAuth registration ID in the redirect URI (see below). |
name | Yes | -- | Display name shown on the login screen. |
authentication-system | Yes | -- | Name of a provider configured under fire-arrow.authentication.providers. The BFF discovers the OAuth endpoints from that provider's issuer. |
client-id | Yes | -- | OAuth client ID registered at the identity provider. |
client-secret | Yes | -- | Client secret for the confidential client. Supply it through an environment variable or secret store; never commit it. |
scopes | No | openid profile offline_access | Space-separated scopes requested at sign-in. offline_access is no longer required in the browser; request it only if your provider needs it to issue a refresh token for server-side refresh. |
client-authentication-method | No | client_secret_basic | How the server authenticates to the identity provider's token endpoint. Set to private_key_jwt for private-key JWT client authentication. |
post-logout-redirect-uri | No | -- | Where the identity provider returns the browser after single sign-out. |
To disable web UI authentication, remove the web-ui-auth section or set profiles: []. When authentication is disabled server-wide (AUTH_ENABLED=false), the BFF does not activate and the UI renders without a login.
Registering the Client at Your Identity Provider
Each profile corresponds to a confidential (web) client registration at your identity provider. When you create or update that registration:
-
Register it as a confidential/web client (not a public/SPA client) and generate a client secret.
-
Set the redirect URI to the BFF callback. The path is
/bff/login/oauth2/code/<profile>, where<profile>is the profile'sprofilevalue. For a profile namedpatient-loginserved fromfhir.example.com:https://fhir.example.com/bff/login/oauth2/code/patient-login -
If you use single sign-out, register the post-logout redirect URI you configured in
post-logout-redirect-uri, for examplehttps://fhir.example.com/admin/login.
Content Security Policy for External Origins
The admin UI ships with a Content Security Policy and HSTS configured under fire-arrow.admin.security-headers. The defaults are deployment-agnostic and need no change for a standard setup.
If your identity provider, object storage, or another dependency is served from a different origin than the server, add those origins to the CSP so the login redirect and API calls are not blocked:
fire-arrow:
admin:
security-headers:
csp:
additional-connect-src: ["https://login.example-idp.com"]
additional-frame-src: []
additional-script-src: []
hsts:
enabled: true
max-age-seconds: 31536000
include-sub-domains: false
include-sub-domains defaults to false so that HSTS is safe to enable on deployments that share a parent domain with unrelated subdomains.
Session Storage
The BFF stores its sessions and the server-side authorized-client records (which hold the access and refresh tokens) in the server's PostgreSQL database. The required tables are created automatically by Flyway on the first startup after upgrade — no manual database step is needed beyond the migration permissions the server already uses.
Upgrading from the Browser-Based Login
Before 1.14.0, each web UI login profile was a public OAuth client. Tokens were obtained in the browser and kept in localStorage, and each profile needed only a client-id:
# Pre-1.14.0 — no longer valid on its own
fire-arrow:
admin:
web-ui-auth:
profiles:
- profile: patient-login
name: Patient
authentication-system: azure-b2c
client-id: "${OIDC_PATIENT_CLIENT_ID}"
scopes: "openid offline_access"
To upgrade:
- At the identity provider, convert each web UI client registration from public/SPA to confidential, generate a client secret, and change the redirect URI to
/bff/login/oauth2/code/<profile>. Remove the old browser callback (for example/admin/login/callback). - In
application.yaml, addclient-secretto every profile. You can simplifyscopestoopenid profile, since refresh no longer happens in the browser. - If your identity provider is on a different origin, add it to
security-headers.csp.additional-connect-src.
If any profile is missing client-secret, startup fails with an error naming the offending profile. Add the secret to every profile before deploying 1.14.0.