OIDC / OAuth 2.0 Providers
Plugwerk supports browser-based "Sign in with …" flows against external OpenID Connect and OAuth 2.0 providers. This page is the operator-facing setup guide: how to register a client application at each provider, how to enter the credentials in Plugwerk, and how the Plugwerk-specific identity model behaves on first login.
How it works
Section titled “How it works”Plugwerk uses Spring Security's built-in oauth2Login to perform a token-exchange:
- The user clicks Sign in with
<Provider>on the Plugwerk login page. - The browser is redirected to the provider's authorize endpoint with a PKCE-protected authorization request.
- The provider authenticates the user, then redirects back to Plugwerk's callback URL with an authorization code.
- Plugwerk's backend exchanges the code for tokens server-side, validates the ID token, and either creates or looks up a
plugwerk_userrow. - From this point the session is byte-for-byte identical to a local-login session — same Plugwerk-issued JWT, same refresh cookie. The OIDC token is discarded.
The Plugwerk callback URL the provider must redirect to is:
https://<your-plugwerk-host>/login/oauth2/code/<oidc_provider.id><oidc_provider.id> is the UUID Plugwerk assigns when you register the provider. Because that UUID does not exist until you save the provider in Plugwerk, the practical workflow is:
- Stub the provider in Plugwerk first (any callback URL works for the save).
- Copy the assigned UUID and build the full callback URL.
- Enter that callback URL at the IdP.
- Test the login. If the IdP requires the URL up front, use a wildcard pattern (
https://<plugwerk-host>/login/oauth2/code/*) where the provider supports it, or repeat the round-trip.
Plugwerk-specific identity model
Section titled “Plugwerk-specific identity model”Three policies are baked into the schema and worth understanding before you onboard users:
- One identity per
(provider, sub). Each OIDC subject maps to exactly one Plugwerk user. AUNIQUE(user_id)constraint onoidc_identityenforces this at the database level. - No cross-provider linking. A user who first signs in via Keycloak and later via GitHub gets two unrelated Plugwerk accounts. This is by design — the no-linking policy makes auto-provisioning safe and avoids merge foot-guns. Communicate one provider per user where possible.
- JIT user has no namespace memberships. A first successful OIDC callback creates a
plugwerk_userrow withenabled=true,is_superadmin=false, and zero namespace roles. The user lands on a welcome page asking them to contact an administrator. A namespace admin (or a superadmin) then grants access viaPOST /api/v1/namespaces/{ns}/members.
The email claim is mandatory — see Troubleshooting for the provider-specific failure modes.
Provider types
Section titled “Provider types”Plugwerk's OidcProviderType enum has five values. Pick the type that matches your IdP — the required fields differ per type.
| Type | Use for | Required fields besides name / clientId / clientSecret |
| ---------- | ---------------------------------------------------- | -------------------------------------------------------------------- |
| OIDC | Any standards-compliant OIDC IdP — Keycloak, Authentik, Auth0, Dex, Microsoft Entra ID, Google Workspace via OIDC, … | issuerUri (Plugwerk auto-discovers all endpoints from ${issuerUri}/.well-known/openid-configuration) |
| GITHUB | GitHub OAuth Apps | None — endpoints are hard-wired |
| GOOGLE | Google OAuth (consumer + Workspace) | None — endpoints are hard-wired |
| FACEBOOK | Facebook Login | None — endpoints are hard-wired |
| OAUTH2 | OAuth 2.0 sources without OIDC discovery (GitLab, Bitbucket, custom enterprise IdPs) | authorizationUri, tokenUri, userInfoUri; optionally jwkSetUri and the three attribute names |
For OIDC-compliant providers (which includes most modern IdPs), prefer type OIDC over OAUTH2 — discovery removes four configuration knobs and gives Plugwerk JWKS rotation for free.
Prerequisites
Section titled “Prerequisites”- HTTPS in production. Browsers and most IdPs refuse non-HTTPS callback URLs. Configure your reverse proxy first (see Deployment) and set
PLUGWERK_AUTH_TRUSTED_PROXY_CIDRSso the rate-limit buckets stay correct. PLUGWERK_AUTH_ENCRYPTION_KEYis set (see Configuration). Plugwerk encrypts everyclientSecretat rest with AES-256-CBC; without the key, provider creation fails. Rotating the key invalidates every stored secret in the database, so plan a re-rotation across all configured providers if you ever change it.- Reachable discovery URL. For type
OIDC, the server fetches${issuerUri}/.well-known/openid-configurationat provider creation and at every login — the issuer must be reachable from the Plugwerk container. - Publicly resolvable issuer host. Plugwerk's SSRF guard rejects private (RFC 1918), loopback, link-local, and metadata-service hosts at write time and at every registry refresh. For local development against an IdP on
localhost, see the Local Keycloak for development Aside below; for the full configuration surface (the two override blocklists and the escape hatch), see OIDC SSRF guard.
Register a provider in Plugwerk
Section titled “Register a provider in Plugwerk”You can register providers via the Admin UI or via the REST API. Both write to the same oidc_provider table; the choice is operational preference.
Via the Admin UI
Section titled “Via the Admin UI”- Sign in as a superadmin and open Settings → OIDC Providers.
- Click Add provider and pick the provider type.
- Fill in
name(the label shown on the login page),clientId, andclientSecret. For typeOIDCalso enter theissuerUri. - Save. Plugwerk assigns the provider a UUID.
- Copy the UUID and build the callback URL:
https://<your-plugwerk-host>/login/oauth2/code/<uuid>. - Go to the IdP and enter that callback URL on the client-app configuration.
- Back in Plugwerk, enable the provider. It now appears as a button on the login page.
Via the REST API
Section titled “Via the REST API”All /admin/oidc-providers endpoints require a superadmin Bearer token.
Probe an issuer URI before saving — useful to catch typos and unreachable IdPs without polluting the provider table:
curl -X POST "https://<plugwerk-host>/api/v1/admin/oidc-providers/discover" \ -H "Authorization: Bearer <superadmin-jwt>" \ -H "Content-Type: application/json" \ -d '{ "issuerUri": "https://keycloak.example.com/realms/plugwerk" }'A successful response carries the four discovered endpoints (authorizationUri, tokenUri, userInfoUri, jwkSetUri); a failure response carries an error string with an actionable reason (DNS failure, 404, malformed JSON, …).
Create a provider:
curl -X POST "https://<plugwerk-host>/api/v1/admin/oidc-providers" \ -H "Authorization: Bearer <superadmin-jwt>" \ -H "Content-Type: application/json" \ -d '{ "name": "Keycloak", "providerType": "OIDC", "clientId": "plugwerk", "clientSecret": "<copied from Keycloak>", "issuerUri": "https://keycloak.example.com/realms/plugwerk", "scope": "openid email profile" }'The response includes the assigned id (UUID) — that is the value to plug into the IdP callback URL.
Update fields later (partial — only the keys you include are changed):
curl -X PATCH "https://<plugwerk-host>/api/v1/admin/oidc-providers/<provider-uuid>" \ -H "Authorization: Bearer <superadmin-jwt>" \ -H "Content-Type: application/json" \ -d '{ "clientSecret": "<rotated value>" }'providerType is intentionally not editable — to switch from OAUTH2 to OIDC (or vice versa) you must delete and recreate the provider.
Provider walkthroughs
Section titled “Provider walkthroughs”Each section has two parts: A — Set up the client app at the provider, and B — Enter the credentials in Plugwerk. Provider UIs change frequently; the field labels below are stable but the navigation paths may shift slightly.
Keycloak
Section titled “Keycloak”A — Create the client in Keycloak
Section titled “A — Create the client in Keycloak”- Sign in to the Keycloak admin console and switch to the realm you want to use (or create one).
- Clients → Create client.
- Client type: OpenID Connect. Client ID:
plugwerk(any string is fine; you will paste this back in Plugwerk). - Client authentication: ON (Plugwerk needs a confidential client — public clients have no client secret).
- Authentication flow: Standard flow ON; everything else off unless you know you need it.
- Valid redirect URIs:
https://<your-plugwerk-host>/login/oauth2/code/<plugwerk-provider-uuid>— or, during initial setup, the wildcardhttps://<your-plugwerk-host>/login/oauth2/code/*. - Save. Open the Credentials tab and copy the Client secret.
B — Register in Plugwerk
Section titled “B — Register in Plugwerk”- Type:
OIDC - Issuer URI:
https://<keycloak-host>/realms/<realm-name> - Client ID:
plugwerk(or whatever you set in step 3) - Client secret: the value from the Credentials tab
- Scope: keep the default
openid email profile
A — Create the application in Auth0
Section titled “A — Create the application in Auth0”- Auth0 Dashboard → Applications → Create Application.
- Name the application; choose Regular Web Application.
- Open the application's Settings.
- Allowed Callback URLs:
https://<your-plugwerk-host>/login/oauth2/code/<plugwerk-provider-uuid>. - (Optional) Allowed Logout URLs:
https://<your-plugwerk-host>/loginso RP-Initiated Logout returns to your login page. - Save changes. Note the Domain, Client ID, and Client Secret at the top of the Settings page.
B — Register in Plugwerk
Section titled “B — Register in Plugwerk”- Type:
OIDC - Issuer URI:
https://<your-tenant>.auth0.com/(note the trailing slash — Auth0 includes it in theissclaim) - Client ID and Client Secret: copied from Auth0
- Scope:
openid email profile
Microsoft Entra ID (Azure AD)
Section titled “Microsoft Entra ID (Azure AD)”A — Register the app in Entra
Section titled “A — Register the app in Entra”- Azure Portal → Microsoft Entra ID → App registrations → New registration.
- Name the application. Pick the Supported account types that match your tenant policy.
- Redirect URI: platform Web, value
https://<your-plugwerk-host>/login/oauth2/code/<plugwerk-provider-uuid>. - Register. From the Overview page, copy the Application (client) ID and the Directory (tenant) ID.
- Certificates & secrets → Client secrets → New client secret. Copy the Value immediately — it is shown only once.
- API permissions: the default
Microsoft Graph → User.Readis enough; addemailandprofileonly if your tenant overrides the defaults away from them.
B — Register in Plugwerk
Section titled “B — Register in Plugwerk”- Type:
OIDC - Issuer URI:
https://login.microsoftonline.com/<tenant-id>/v2.0 - Client ID: the Application (client) ID
- Client Secret: the secret Value
- Scope:
openid email profile
A — Create the OAuth client in Google Cloud
Section titled “A — Create the OAuth client in Google Cloud”- Google Cloud Console → APIs & Services → OAuth consent screen. Configure user type, app info, and add
emailto the requested scopes if it isn't already implicit. Without this step the credential creation form is locked. - APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Application type: Web application.
- Authorized redirect URIs:
https://<your-plugwerk-host>/login/oauth2/code/<plugwerk-provider-uuid>. - Create. Copy the Client ID and Client secret from the dialog.
B — Register in Plugwerk
Section titled “B — Register in Plugwerk”- Type:
GOOGLE(endpoints are hard-wired, noissuerUrineeded) - Client ID and Client Secret: copied from Google
- Scope: keep the default
openid email profile
GitHub
Section titled “GitHub”A — Create the OAuth App on GitHub
Section titled “A — Create the OAuth App on GitHub”- GitHub → your profile menu → Settings → Developer settings → OAuth Apps → New OAuth App.
- Application name and Homepage URL (
https://<your-plugwerk-host>). - Authorization callback URL:
https://<your-plugwerk-host>/login/oauth2/code/<plugwerk-provider-uuid>. - Register. From the app page, Generate a new client secret and copy the value immediately.
- Copy the Client ID as well.
B — Register in Plugwerk
Section titled “B — Register in Plugwerk”- Type:
GITHUB - Client ID and Client Secret: copied from GitHub
Facebook is supported as type FACEBOOK and the setup is mechanically identical to Google: create an app in the Facebook for Developers console, add a redirect URI, copy the App ID and App Secret. The first-login email failure mode is also similar (Facebook App Review must approve the email permission for your app before it returns the claim). Use only when your audience genuinely sits on Facebook accounts; for B2B Plugwerk instances Keycloak / Entra ID / Google Workspace are the more common picks.
Generic OAuth 2.0 (GitLab, Bitbucket, custom IdPs)
Section titled “Generic OAuth 2.0 (GitLab, Bitbucket, custom IdPs)”Use type OAUTH2 for any OAuth 2.0 source that is not OIDC-compliant or that the four hard-wired vendors above don't cover. You configure all four endpoints manually plus the JSON attribute names that user-info uses.
curl -X POST "https://<plugwerk-host>/api/v1/admin/oidc-providers" \ -H "Authorization: Bearer <superadmin-jwt>" \ -H "Content-Type: application/json" \ -d '{ "name": "GitLab", "providerType": "OAUTH2", "clientId": "<gitlab-application-id>", "clientSecret": "<gitlab-application-secret>", "scope": "read_user email", "authorizationUri": "https://gitlab.com/oauth/authorize", "tokenUri": "https://gitlab.com/oauth/token", "userInfoUri": "https://gitlab.com/api/v4/user", "subjectAttribute": "id", "emailAttribute": "email", "displayNameAttribute": "name" }'subjectAttribute, emailAttribute, displayNameAttribute default to sub, email, name respectively if omitted. They are only meaningful for type OAUTH2; for type OIDC the standard claim names are used and these fields are ignored.
For GitLab and other providers that do expose an OIDC discovery document (https://gitlab.com/.well-known/openid-configuration), prefer type OIDC — fewer knobs, JWKS rotation handled automatically.
Login flow and account provisioning
Section titled “Login flow and account provisioning”How providers appear on the login page
Section titled “How providers appear on the login page”The public GET /api/v1/config endpoint returns enabled providers in the order they were created. For each provider the SPA renders a Sign in with <name> button using the provider's iconKind (github, google, facebook, oidc, oauth2) for the glyph.
Each button hits GET /oauth2/authorization/{provider-uuid}, which is Spring Security's authorize-start endpoint. PKCE verifier and OAuth2 state are stashed in the HTTP session (httpOnly + SameSite + Secure in production) until the callback consumes them.
Account switching
Section titled “Account switching”Users who want to sign in as a different upstream account (e.g. switching between two Google accounts) need the IdP to re-prompt instead of silently re-authenticating the existing session. Plugwerk surfaces an account-picker URL per provider that appends the right OIDC prompt parameter:
| Provider type | prompt value used | Notes |
| ------------- | ---------------------- | ----------------------------------------------------- |
| OIDC, GOOGLE, FACEBOOK | select_account | Standard OIDC prompt — most providers honour it |
| OAUTH2 | login | Best-effort; some OAuth2 providers ignore prompt |
| GITHUB | (none) | GitHub does not support prompt. Plugwerk surfaces a side-link to https://github.com/logout instead so the user can terminate the GitHub session before retrying |
First successful callback (JIT provisioning)
Section titled “First successful callback (JIT provisioning)”On the first successful callback for a (provider, sub) pair Plugwerk creates:
- a
plugwerk_userrow withdisplay_namefrom thenameclaim (falling back topreferred_username, thensubject) - an
oidc_identityrow that ties this Plugwerk user to the provider UUID + IdP subject enabled=true,is_superadmin=false, no namespace memberships
The user lands on a welcome page that hides the "Create Namespace" button (non-superadmins cannot create namespaces) and displays a hint to ask an administrator. To grant access, a namespace admin runs:
curl -X POST "https://<plugwerk-host>/api/v1/namespaces/<ns-slug>/members" \ -H "Authorization: Bearer <admin-jwt>" \ -H "Content-Type: application/json" \ -d '{ "userId": "<plugwerk-user-uuid>", "role": "MEMBER" }'(Roles: ADMIN, MEMBER, READ_ONLY — see Concepts.)
Logout
Section titled “Logout”POST /api/v1/auth/logout is provider-aware:
- Local sessions →
204 No Content. The SPA clears its store and routes to/login. - OIDC sessions →
200 OKwithLogoutResponse{ endSessionUrl }. The SPA navigateswindow.location.assign(endSessionUrl). The IdP destroys its own session, then bounces the browser back to${plugwerk-host}/loginvia thepost_logout_redirect_uriparameter Plugwerk fills in. Without this navigation the next "Sign in with<provider>" click would silently re-authenticate the same user.
Providers without an end_session_endpoint (vanilla OAuth2 surfaces) get a graceful fallback: the backend returns 204 and there is no IdP-side cleanup. This is documented as a known limitation for type OAUTH2.
Managing existing providers
Section titled “Managing existing providers”Update fields with PATCH
Section titled “Update fields with PATCH”Only the fields you include change. clientSecret is writeOnly — omit it (or send blank) to leave the stored value intact, send a value to rotate.
# Rotate the client secretcurl -X PATCH "https://<plugwerk-host>/api/v1/admin/oidc-providers/<uuid>" \ -H "Authorization: Bearer <superadmin-jwt>" \ -H "Content-Type: application/json" \ -d '{ "clientSecret": "<new value>" }'
# Disable temporarily (keeps the row, hides the login button)curl -X PATCH "https://<plugwerk-host>/api/v1/admin/oidc-providers/<uuid>" \ -H "Authorization: Bearer <superadmin-jwt>" \ -H "Content-Type: application/json" \ -d '{ "enabled": false }'Constraints to be aware of:
providerTypeis not editable. To switch types: delete and recreate.- Changing
clientIdon an enabled provider invalidates every existing access token issued by that provider (theaudclaim no longer matches). Users will be forced to sign in again. - For type
OIDC,scopemust includeopenid. Patches that violate this are rejected. issuerUriandjwkSetUricannot be cleared viaPATCH. To remove either, delete and recreate the provider.
Delete a provider
Section titled “Delete a provider”DELETE /api/v1/admin/oidc-providers/<uuid> removes the provider row. Plugwerk handles the dependent data conservatively:
- The OIDC-identity records for this provider are removed.
- The orphaned Plugwerk user accounts survive with
enabled=false. Audit history (namespace memberships, refresh-token records, download events) is preserved. - Disabled users cannot log in. An administrator can review them and hard-delete them via the user-admin UI later.
This is the right behaviour for "we are rotating IdPs" or "this provider was a mistake" — it preserves the audit trail without keeping accounts that can no longer authenticate active.
Security and best practices
Section titled “Security and best practices”- HTTPS-only callbacks in production. Plain-HTTP redirect URIs are rejected by most modern IdPs and will leak authorization codes.
- Rotate client secrets periodically via
PATCH { "clientSecret": "..." }. Spring Security picks up the new value on the next authorize-start request — no server restart needed. - Minimum scope is
openid email profilefor typeOIDC. Plugwerk needsemailto provision the user;profileis what populatesdisplay_name. Don't grant scopes you don't use. - Treat
PLUGWERK_AUTH_ENCRYPTION_KEYas a long-lived secret. Rotating it requires re-saving every provider'sclientSecret— plan a maintenance window. - Communicate the one-provider-per-user policy to your users. Two providers = two accounts; account-linking is intentionally not supported.
- Use the lab
/discoverendpoint before saving a Generic OIDC provider — it catches typos inissuerUriand unreachable IdPs without polluting your provider list. - Keep the OIDC SSRF guard on in production.
PLUGWERK_AUTH_OIDC_ALLOW_PRIVATE_DISCOVERY_URISdefaults tofalsefor a reason — the guard rejects metadata-service hosts (169.254.169.254,metadata.google.internal) that an attacker could otherwise use as the issuer URI of a malicious provider to coerce the server into making outbound requests against your cloud's instance metadata. The server logs a startup WARN line whenever the escape hatch is enabled. See OIDC SSRF guard for the configuration surface. - Consider enabling
PLUGWERK_AUTH_OIDC_FAIL_FAST_ON_UNDECRYPTABLE_PROVIDERS=truein production. When the server cannot decrypt a stored client secret (typically after aPLUGWERK_AUTH_ENCRYPTION_KEYrotation without a re-encrypt step, a DB restore, or a deployment-environment switch), the default behaviour is to log the affected provider atERRORand silently skip it — Sign-in with that provider then 4xx's at runtime while the server saysUP. Fail-fast mode refuses to start until the affected secrets are re-entered or the previous key is restored, surfacing the misconfiguration in your deployment pipeline rather than at the user's login button. See OIDC encryption-key safety.
Troubleshooting
Section titled “Troubleshooting”"OIDC provider returned no email claim"
Section titled “"OIDC provider returned no email claim"”This is the most common first-login failure. Plugwerk requires an email for every user; a callback without one is rejected with HTTP 400 and a provider-aware message:
| Provider | Likely cause | Fix |
| -------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Keycloak / Authentik / generic OIDC | email scope missing from the requested scope list, or the IdP is not configured to release the email mapper | Add email to scope; on Keycloak verify the email user attribute is mapped to the client |
| Auth0 | Same — scope missing or the user has no email on file | Add email to scope; ensure the connection populates email |
| Microsoft Entra ID | email scope missing, or the user is a guest account without a mail attribute set | Add email to scope; for guests the Object ID's mail field must be populated |
| Google | OAuth consent screen does not request email | OAuth consent screen → add email and profile |
| GitHub | The user's primary email is private | The user goes to Settings → Emails and unchecks "Keep my email addresses private", then retries the login |
| Facebook | The Facebook App has not been approved for the email permission | Run the Facebook App Review for email permission |
| Generic OAUTH2 | The configured emailAttribute is absent from the user-info response | PATCH the provider with the correct emailAttribute for your IdP |
"Invalid redirect_uri" / 400 from the IdP
Section titled “"Invalid redirect_uri" / 400 from the IdP”The callback URL the IdP sees does not match what is registered on the client app. Check:
- The Plugwerk provider UUID in the URL — it is the live UUID from
oidc_provider.id, not a placeholder. - HTTPS vs HTTP — most IdPs reject mismatched schemes even on
localhost. - Trailing slash — Plugwerk emits the URL without a trailing slash; some IdPs do strict-equality matching.
If the IdP supports wildcards in callback URLs, a temporary https://<plugwerk-host>/login/oauth2/code/* simplifies the round-trip during initial setup.
"Issuer URI host is blocked" / discovery refused
Section titled “"Issuer URI host is blocked" / discovery refused”Plugwerk's SSRF guard rejected the host before the discovery request was made. Two paths:
- Public IdP — the host matched one of the two configurable blocklists. Inspect
PLUGWERK_AUTH_OIDC_BLOCKED_HOST_NAMESandPLUGWERK_AUTH_OIDC_BLOCKED_HOST_SUFFIXES. Note the replace-only semantics: if either variable is set, it has fully replaced the defaults — see OIDC SSRF guard. - Local IdP on
localhost(development) — setPLUGWERK_AUTH_OIDC_ALLOW_PRIVATE_DISCOVERY_URIS=trueon the Plugwerk server. The escape hatch must stayfalsein production.
"Sign in with <provider>" returns 4xx but the server is healthy
Section titled “"Sign in with <provider>" returns 4xx but the server is healthy”The stored client secret cannot be decrypted with the current PLUGWERK_AUTH_ENCRYPTION_KEY — typically because the key rotated without re-encrypting existing secrets, a DB backup was restored under a different key, or the deployment-environment swapped the env var. Server health endpoints stay UP because other providers are unaffected; only the broken provider's login flow fails.
Check the server logs for the structured ERROR line:
Cannot decrypt OIDC provider '<name>' (registrationId=…) client secret. The data wasencrypted with a different key than the current PLUGWERK_AUTH_ENCRYPTION_KEY.Two fixes:
- Re-enter the client secret in the admin UI under Settings → OIDC Providers (or via
PATCH /api/v1/admin/oidc-providers/<uuid>with a freshclientSecret). - Restore the previous
PLUGWERK_AUTH_ENCRYPTION_KEYvalue if the rotation was unintended.
To make this fail at boot rather than at the user's login button — recommended for production — set PLUGWERK_AUTH_OIDC_FAIL_FAST_ON_UNDECRYPTABLE_PROVIDERS=true. See OIDC encryption-key safety.
"Issuer mismatch" / token validation fails right after creation
Section titled “"Issuer mismatch" / token validation fails right after creation”Plugwerk's runtime issuer check compares the iss claim in the ID token to the issuerUri field exactly. Common causes:
- Auth0: trailing slash difference (
https://tenant.auth0.comvshttps://tenant.auth0.com/). Auth0 always sends theisswith a trailing slash — store it that way in Plugwerk. - Keycloak behind a reverse proxy: the
issuerreturned by.well-known/openid-configurationmay be the internal hostname instead of the proxy hostname. ConfigureKC_HOSTNAMEon Keycloak so the discovery document lists the externally reachable URL.
Run POST /admin/oidc-providers/discover with the suspect URI to see the full discovery document the server actually receives.
Login worked yesterday, fails today after a PATCH
Section titled “Login worked yesterday, fails today after a PATCH”Changing clientId on an enabled provider invalidates every currently-valid OIDC-issued access token (aud claim mismatch). Users must sign in again. This is expected and not a bug.
Where to find login logs
Section titled “Where to find login logs”Plugwerk emits OIDC login events at INFO level (OidcLoginSuccessHandler) and rejected callbacks at WARN. See Monitoring for the log scrape setup. Spring Security's OAuth2LoginAuthenticationFilter also emits its own DEBUG entries — enable with logging.level.org.springframework.security.oauth2=DEBUG if you need wire-level detail.
References
Section titled “References”- Plugwerk REST API reference — full schema for the
/admin/oidc-providersendpoints - Authentication overview — local login + API keys
- Configuration — environment variables including
PLUGWERK_AUTH_ENCRYPTION_KEY