Skip to main content

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.

Requires a confidential client

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 localStorage or 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"
PropertyRequiredDefaultDescription
profileYes--Unique identifier for the profile. Also used as the OAuth registration ID in the redirect URI (see below).
nameYes--Display name shown on the login screen.
authentication-systemYes--Name of a provider configured under fire-arrow.authentication.providers. The BFF discovers the OAuth endpoints from that provider's issuer.
client-idYes--OAuth client ID registered at the identity provider.
client-secretYes--Client secret for the confidential client. Supply it through an environment variable or secret store; never commit it.
scopesNoopenid profile offline_accessSpace-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-methodNoclient_secret_basicHow the server authenticates to the identity provider's token endpoint. Set to private_key_jwt for private-key JWT client authentication.
post-logout-redirect-uriNo--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:

  1. Register it as a confidential/web client (not a public/SPA client) and generate a client secret.

  2. Set the redirect URI to the BFF callback. The path is /bff/login/oauth2/code/<profile>, where <profile> is the profile's profile value. For a profile named patient-login served from fhir.example.com:

    https://fhir.example.com/bff/login/oauth2/code/patient-login
  3. If you use single sign-out, register the post-logout redirect URI you configured in post-logout-redirect-uri, for example https://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:

  1. 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).
  2. In application.yaml, add client-secret to every profile. You can simplify scopes to openid profile, since refresh no longer happens in the browser.
  3. If your identity provider is on a different origin, add it to security-headers.csp.additional-connect-src.
The server will not start without a client secret

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.