Skip to main content

Sign-in — local and OIDC

A user signs into Liberty through one of two paths:

PathWhen
Local username + passwordThe user types credentials on the sign-in screen. The framework checks them against auth.toml (or the ly2_users table) and mints a JWT.
OIDCThe user clicks Sign in with <provider>, is redirected to the IdP, signs in there, and returns to Liberty with their identity. The framework provisions / updates the user and mints a JWT.

Both can coexist — a single install offers both buttons on the sign-in screen, and users pick whichever applies.


The sign-in screen

Sign in to LibertyUsernamealicePassword••••••••••Sign inorSign in with Keycloak

When OIDC is configured, a second button appears below the local form. When OIDC isn't configured, only the local form shows.

For installs that want to force OIDC (no local fallback), set [auth] disable_local_login = true in app.toml — the local form hides and only the OIDC button remains.


Step 1 — Pick the identity backend

In app.toml:

[auth]
backend = "toml" # or "db"
ValueStores users inWhen
toml (default)config/auth.tomlSingle-instance installs, small teams. Edits land atomically on disk.
dbThe framework's pool (tables ly2_users, ly2_roles, ly2_user_roles).Multi-instance installs that need a shared user catalogue.

Both backends are managed through the same UISettings → Access. The backend is a deployment choice; the UI doesn't care.

Bootstrap the first user

On a fresh install with no users, run:

liberty-admin init-db --superuser <name>

The command prompts for a password and creates a superuser. Sign in once, then use Settings → Access → Users to add others (see Users).


Step 2 — Configure OIDC (optional)

OIDC is layered on top of the chosen backend. Any OIDC-compliant provider works — Liberty discovers endpoints, JWKS and supported scopes from the provider's .well-known/openid-configuration.

ProviderDiscovery URL pattern
Keycloakhttps://<host>/realms/<realm>/.well-known/openid-configuration
Azure ADhttps://login.microsoftonline.com/<tenant>/v2.0/.well-known/openid-configuration
Auth0https://<tenant>.auth0.com/.well-known/openid-configuration
Oktahttps://<tenant>.okta.com/oauth2/default/.well-known/openid-configuration
Googlehttps://accounts.google.com/.well-known/openid-configuration

Configuration

In app.toml:

[oidc]
enabled = true
discovery_url = "https://keycloak.corp.local/realms/liberty/.well-known/openid-configuration"
client_id = "liberty-app"
client_secret = "${OIDC_CLIENT_SECRET}" # env var — never inline a secret
scopes = "openid email profile"
username_claim = "preferred_username" # which claim becomes the Liberty username
email_claim = "email"
name_claim = "name"
redirect_url = "" # blank = auto-derived as https://<host>/auth/oidc/callback
frontend_redirect = "" # blank = standard server-side flow
FieldRequiredNotes
enabledyesTurn OIDC on / off. When off, the OIDC button hides from the sign-in screen.
discovery_urlyesThe provider's .well-known URL. Liberty fetches once at startup, caches the result.
client_idyesThe OAuth2 client id registered with the provider.
client_secretyesRead from env var (${OIDC_CLIENT_SECRET}). Never inline.
scopesyesAt minimum openid; add email / profile to pull those claims.
username_claimno (defaults to preferred_username)Which claim becomes the Liberty username. Falls back to email, then sub if missing.
email_claimnoWhich claim becomes the user's email.
name_claimnoWhich claim becomes the user's full name.
redirect_urlnoOverride only when the framework is behind a reverse proxy that rewrites the host.
frontend_redirectnoSet for SPA-style flows that handle tokens in the URL fragment. Standard server-side flow leaves it blank.

Register the redirect URL with the provider

The redirect URL Liberty expects is:

https://<your-framework-host>/auth/oidc/callback

Add it to the OAuth2 client's Redirect URIs in the provider's console. Without this, the provider rejects the callback with redirect_uri_mismatch.


Step 3 — What happens on OIDC sign-in

  1. User clicks Sign in with <provider> on the sign-in screen.
  2. Liberty redirects to the provider's authorization endpoint (/auth/oidc/login).
  3. User signs in at the provider — including MFA, password resets, all the IdP's own flows.
  4. The provider redirects back to https://<host>/auth/oidc/callback?code=…&state=….
  5. Liberty exchanges the code for an ID token, validates the signature via JWKS, extracts the claims.
  6. Liberty provisions or updates the local user:
    • First sign-in: a new user row is created with provider = "oidc", provider_subject = <sub>, username = <username_claim>, email, full_name from claims. is_active = true, no roles.
    • Subsequent sign-ins: the email + full name fields are refreshed from the latest claims; roles stay as configured in Liberty.
  7. Liberty mints its own JWT pair (access + refresh) and the user is signed in.

The provider's tokens are not propagated — Liberty issues fresh tokens with its own signing key. The provider's session ends; Liberty's begins.


OIDC users in the Access page

OIDC users appear in Settings → Access → Users only after their first sign-in — Liberty doesn't pre-create them. So the typical onboarding pattern is:

  1. The new hire signs in via OIDC. They land with no roles → they see an empty app.
  2. The operator opens Settings → Access → Users, finds the new user, assigns the right roles.
  3. The user refreshes (or signs back in) and now has access.

For installs with many OIDC users, an automation script can pre-create them via POST /admin/access/users so roles are ready at first sign-in — but the standard flow assumes manual role assignment.

See Users for the assignment UI.


Disabling local login

For OIDC-only installs that want to force every user through the IdP:

[auth]
disable_local_login = true

The sign-in screen hides the local form; only the OIDC button shows. Don't disable local login until you've verified OIDC works — locking yourself out of the only sign-in path is a frustrating recovery exercise.

For an emergency rescue with disable_local_login = true, edit app.toml directly on disk to flip the flag back, restart the framework, sign in locally, then re-enable.


Tokens — the JWT pair

When sign-in succeeds (local or OIDC), Liberty mints two tokens:

TokenDefault lifetimeWhat it carries
Access token1 hourThe user's username, roles list, permissions snapshot. Sent on every API call as Authorization: Bearer <token>.
Refresh token14 daysUsed at POST /auth/refresh to mint a new access token without re-typing credentials.

Lifetimes are configurable:

[auth]
access_token_ttl_seconds = 3600 # default 1h
refresh_token_ttl_seconds = 1209600 # default 14 days

Short access TTLs (e.g. 15 minutes) make permission changes propagate faster but increase the refresh load. The default 1-hour balance fits most installs.


Sign-out

Sign-out is client-side only — the access token is removed from local storage and the user redirects to the sign-in screen. The token itself remains valid until its TTL expires (it just can't be re-presented without the storage).

For OIDC installs that want provider sign-out (terminate the IdP session too), point the provider's Post-logout redirect URI at Liberty and configure the provider's end-session endpoint. Liberty doesn't initiate provider logout itself.


Common pitfalls

MistakeSymptomFix
Inlined client_secret in app.toml.The secret leaks into version control.Use ${OIDC_CLIENT_SECRET} and set the env var.
redirect_url doesn't match what's registered with the provider.Sign-in returns redirect_uri_mismatch.Register the exact URL Liberty sends — including the scheme and port.
discovery_url returns 404 or HTML.Sign-in fails at startup with a config error.Verify the URL — most providers add /.well-known/openid-configuration to a base URL; Liberty needs the full path.
username_claim not present in the token.First sign-in fails with "no username".Either change the claim to one the IdP actually emits (email works for most) or configure the IdP to emit the expected claim.
OIDC user signs in but sees an empty app.Expected — OIDC users land with no roles.Operator assigns roles via Settings → Access → Users.
Forgot to enable OIDC after configuring it.The OIDC button doesn't show.enabled = true.
disable_local_login = true without a working OIDC setup.Nobody can sign in.Disable the flag in app.toml directly on disk, restart, fix OIDC, then re-enable.

What's next