Sign-in — local and OIDC
A user signs into Liberty through one of two paths:
| Path | When |
|---|---|
| Local username + password | The user types credentials on the sign-in screen. The framework checks them against the stored users and mints a JWT. |
| OIDC | The user clicks Sign in with OIDC, 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
When OIDC is configured, a second button appears below the local form. When OIDC isn't configured, only the local form shows.
Step 1 — Local users
Local users and roles are managed in Liberty Next → Administer → Access. The installation chooses where they are stored (the server's configuration folder, or the framework's database for several instances sharing one user list); the screens are the same either way.
Bootstrap the first user
On a fresh install with no users, run:
liberty-admin init-db
The command creates the admin superuser and prints a random password once (--admin-username, --password-env to choose them). The Docker installation runs it for you. Sign in once, then use Liberty Next → Administer → Access → Users to add others (see Users).
Step 2 — Configure OIDC (optional)
OIDC is layered on top of the local users. Any OIDC-compliant provider works — Liberty discovers endpoints, JWKS and supported scopes from the provider's .well-known/openid-configuration.
| Provider | Discovery URL pattern |
|---|---|
| Keycloak | https://<host>/realms/<realm>/.well-known/openid-configuration |
| Azure AD | https://login.microsoftonline.com/<tenant>/v2.0/.well-known/openid-configuration |
| Auth0 | https://<tenant>.auth0.com/.well-known/openid-configuration |
| Okta | https://<tenant>.okta.com/oauth2/default/.well-known/openid-configuration |
https://accounts.google.com/.well-known/openid-configuration |
Configuration
OIDC is set in Liberty Next → Administer → App → OpenID Connect (SSO). The client secret is encrypted when saved, in a masked input with an eye to show it. Saving applies the change at once — no restart; the next sign-in uses it.
| Field | Required | Notes |
|---|---|---|
| Enabled | yes | Shows or hides the Sign in with OIDC button. |
| Discovery URL | yes | The provider's .well-known/openid-configuration URL. |
| Client ID | yes | The client id registered at the provider. |
| Client secret | yes | Encrypted when saved. |
| Scopes | yes | At least openid; email and profile bring those claims (openid email profile by default). |
| Username claim | no | The claim that becomes the Liberty user name (preferred_username by default; then email, then sub when absent). |
| Email claim, Name claim | no | The claims that fill the e-mail address and the full name. |
| Redirect URL override (proxy) | no | Only behind a reverse proxy that rewrites the host. |
| Frontend redirect | no | Only for a front end that handles the tokens itself. |
See App settings → OpenID Connect.
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
- User clicks Sign in with OIDC on the sign-in screen.
- Liberty redirects to the provider's authorization endpoint (
/auth/oidc/login). - User signs in at the provider — including MFA, password resets, all the IdP's own flows.
- The provider redirects back to
https://<host>/auth/oidc/callback?code=…&state=…. - Liberty exchanges the code for an ID token, validates the signature via JWKS, extracts the claims.
- 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_namefrom 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.
- First sign-in: a new user row is created with
- 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 Liberty Next → Administer → Access → Users only after their first sign-in — Liberty doesn't pre-create them. So the typical onboarding pattern is:
- The new hire signs in via OIDC. They land with no roles → they see an empty app.
- The operator opens Liberty Next → Administer → Access → Users, finds the new user, assigns the right roles.
- 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.
A user signed in through OIDC has no local password: their Profile window has no Security tab.
Tokens — the JWT pair
When sign-in succeeds (local or OIDC), Liberty mints two tokens:
| Token | Default lifetime | What it carries |
|---|---|---|
| Access token | 1 hour | The user's username, roles, permissions, attributes and resolved data scope. Sent on every API call as Authorization: Bearer <token>. |
| Refresh token | 14 days | Used at POST /auth/refresh to mint a new access token without re-typing credentials. |
Refresh tokens are single-use. Each POST /auth/refresh returns a new refresh token and consumes the one presented. The same token presented again more than 60 seconds later (two browser tabs racing) is refused. An API client must store the refresh token of every reply. The renewal also reads the user again: a deactivated user is refused, and changed roles, attributes and data scope apply from that moment.
Silent refresh
The refresh is silent — the operator never sees a "session expired" prompt on an active browser session:
- A proactive timer runs the refresh a short window before the access token expires, so the next API call already carries a fresh token.
- Any API call that comes back 401 triggers a refresh-and-retry: Liberty asks the server for a new access token with the refresh token, replays the original request, and returns the result to the caller. The single 401 is transparent to the calling code.
- If the refresh itself is refused (refresh token expired or revoked), the sign-in screen takes over — this is the only path where the user has to re-authenticate.
The two paths are complementary. The proactive timer covers the common case where the session is quiet enough that no request would notice the expiring token; the 401-retry catches the corner case where the timer missed (a paused tab, a device coming out of sleep). Together they make an active session survive the access token's TTL without visible interruption.
Too many failed sign-ins
After 10 failed sign-ins for one username within 5 minutes, /auth/login answers 429 — Too many failed logins — try again in N s until the window has passed. From one client address the limit is five times higher. A successful sign-in clears the count for that user.
The counts are kept in the server's memory: a restart clears them.
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
| Mistake | Symptom | Fix |
|---|---|---|
| The redirect URL registered at the provider differs from Liberty's. | Sign-in returns redirect_uri_mismatch. | Register the exact URL Liberty sends — including the scheme and port. |
| The 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. |
| The Username claim is not 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 Liberty Next → Administer → Access → Users. |
| OIDC configured but not enabled. | The OIDC button doesn't show. | Tick Enabled. |
What's next
- Users — add local users, manage OIDC users post-first-sign-in, assign roles.
- Roles and permissions — what to assign once a user has signed in.
- Concepts → Authentication — the deep reference.