Skip to content

Configuring Authentik OIDC for an app

Most of the time you don't need this guide. The scaffold script writes a per-app blueprint to services/auth/blueprints/apps/<slug>.yaml, and Authentik's worker reconciles it within ~60s (or instantly with cd services/auth && docker compose restart worker). The blueprint creates the Provider, Application, and group binding with all the right values. This guide is the manual UI fallback — useful for diagnosing blueprint failures, making one-off tweaks, or understanding what the blueprint sets.

Every SCALR app authenticates users via Authentik. The default flow is fully declarative: the scaffold script writes a committed .env.app (structural config) and a blueprint YAML (OIDC config); secrets like POSTGRES_PASSWORD come from <repo>/.env.shared — see ADR 0008. If a blueprint fails or you need to inspect/edit a Provider in the UI, the click-by-click below is what the blueprint encodes.

This guide walks through it for template-app (slug template-app, frontend port 5174). Substitute your own slug and port for any other app.


0. Prerequisites

  • Authentik is running: cd services/auth && docker compose up -d. Wait ~30 seconds.
  • You can sign in to http://auth.localhost as akadmin with AUTHENTIK_BOOTSTRAP_PASSWORD from services/auth/.env.
    • First-boot trap: the bootstrap password is read only on the first boot against a fresh Postgres. If you change .env after that, the database keeps the old password. Either docker compose down -v && docker compose up -d (wipes the volumes), or reset in place with docker compose exec worker ak change_password akadmin.

Open the admin UI: click your avatar (top right) → Admin interface, or go directly to http://auth.localhost/if/admin/.


1. Create the OAuth2/OpenID Provider

  1. Left sidebar: Applications → Providers.
  2. Click Create (top-right of the providers list).
  3. In the modal, pick OAuth2/OpenID Provider and click Next.
  4. Fill in:
    • Name: any human-readable label, e.g. template-app-provider. Used only as a display name inside Authentik.
    • Authentication flow: default-authentication-flow. (Click into the dropdown and start typing — pasting alone often doesn't trigger the filter.)
    • Authorization flow: default-provider-authorization-implicit-consent for dev (auto-approves the consent screen). For production, switch to …-explicit-consent so users see a "this app wants access to your profile" page once.
      • Gotcha: these are two different dropdowns with different lists of options. default-authentication-flow will not appear in the Authorization-flow dropdown — that's by design.
      • If both dropdowns are empty: Authentik's default flows ship as blueprints that load on first boot. Visit Customisation → Blueprints in the admin UI; if any of the default blueprints are in failed state, the dropdowns won't populate. Restart the worker and wait a minute.
  5. Scroll to Protocol settings:
    • Client type: Public. (Critical. Single-page apps can't safely keep a client secret. Picking "Confidential" will break the SPA login.)
    • Client ID: the app's slug — for example, template-app. Authentik prefills this with a random string; replace it with the slug. The whole template wires AUTH_AUDIENCE, VITE_AUTH_CLIENT_ID, the manifest's auth.authentik_app_slug, and the OIDC issuer URL all to this same value, so keeping them aligned saves debugging.
    • Client Secret: leave empty.
    • Redirect URIs/Origins: one per line. For dev, list all four forms — both the gateway hostname and the direct-port URL, each with and without trailing slash:
      http://localhost:5174
      http://localhost:5174/
      http://template-app.localhost
      http://template-app.localhost/
      
      Substitute your slug + port. Why all four:
      • <slug>.localhost is the canonical URL through the Traefik gateway (what users actually click).
      • localhost:<port> is needed for direct-port standalone mode (rare, but useful when bringing one app up in isolation).
      • The trailing-slash variants exist because OIDC libraries differ on whether they include the slash in redirect_uri — register both, ship. For prod, replace localhost with scalr.com (HTTPS) and drop the direct-port URLs.
    • Signing Key: authentik Self-signed Certificate (the only one present after a fresh boot — fine for dev).
  6. Expand Advanced protocol settings:
    • Subject mode: Based on the User's hashed ID is safest. (Based on the User's username is fine for dev — it makes the sub claim more readable in logs.)
    • Include claims in id_token: leave checked.
    • Issuer mode: leave on the default ("Each provider has a different issuer, based on the application slug"). This is what makes the issuer URL http://auth.localhost/application/o/<slug>/, which is what each app's .env.app expects (AUTH_ISSUER, VITE_AUTH_AUTHORITY).
  7. Click Create.

2. Create the Application

  1. Left sidebar: Applications → Applications.
  2. Click Create.
  3. Fill in:
    • Name: human-readable, e.g. Template App. This is what the SCALR portal will eventually show in the app hub.
    • Slug: must equal auth.authentik_app_slug from your manifest. For the template, template-app. The portal's auto-discover keys off this.
    • Provider: pick the one you just made (template-app-provider).
    • Backchannel providers: leave empty.
    • UI settings → Launch URL: http://template-app.localhost (the gateway URL — substitute your slug). This is what users click from the SCALR portal and from Authentik's launchpad. Don't use localhost:<port> — that bypasses Traefik and breaks /api same-origin routing.
    • Policy engine mode: any is fine for dev.
  4. Click Create.

3. Grant access

By default an Application has no policy binding, which means every signed-in Authentik user can access it. That works for dev — the test user can log in immediately.

To restrict access (closer to production behaviour):

  1. Open the application you just created (click its row).
  2. Switch to the Policy / Group / User Bindings tab.
  3. Click Create binding → choose Group.
  4. Pick scalr-users (created by services/auth/blueprints/scalr-test-user.yaml) → Save.

The test user is already a member of scalr-users, so it can sign in. Add akadmin to the group too if you want to test as admin.


4. Verify the OIDC discovery endpoint

This is the single best sanity check. Open in a browser:

http://auth.localhost/application/o/template-app/.well-known/openid-configuration

(Substitute your slug.) You should get a JSON response. The fields map directly to your app's .env.app:

JSON field env var
issuer AUTH_ISSUER and VITE_AUTH_AUTHORITY (drop the trailing slash for the latter)
jwks_uri AUTH_JWKS_URL
token_endpoint what scripts/smoke-test.sh POSTs to
authorization_endpoint where the frontend redirects users to sign in

AUTH_AUDIENCE and VITE_AUTH_CLIENT_ID are both just the slug.

If this URL returns 404, the Application slug doesn't match the URL. Fix the slug in Authentik or in the URL.


5. The smoke test's password-grant requirement

The third check in scripts/smoke-test.sh exchanges the test user's password for a token using the OIDC password grant (grant_type=password). For this to work:

  • The provider's Authentication flow must be default-authentication-flow (set in step 1.4 above).
  • Client type must be Public and Client ID must equal the audience (set in step 1.5).
  • The test user must be a member of any group bound to the application (see step 3).

If the smoke test fails with invalid_grant or unauthorized_client, check those three first. Authentik's logs (docker compose logs server -f) will show the exact reason.


Summary checklist

For an app with slug <slug> and frontend port <port>:

  • [ ] Authentik is up at http://auth.localhost, you can sign in as akadmin.
  • [ ] Provider created with: Client type=Public, Client ID=<slug>, Redirect URIs include all four of http://localhost:<port>, http://localhost:<port>/, http://<slug>.localhost, http://<slug>.localhost/. Default authentication and implicit-consent authorization flows.
  • [ ] Application created with: slug=<slug>, provider=the one you just made, launch URL=http://<slug>.localhost.
  • [ ] (Optional) Application bound to the scalr-users group.
  • [ ] http://auth.localhost/application/o/<slug>/.well-known/openid-configuration returns JSON.
  • [ ] App's .env.app has AUTH_AUDIENCE=<slug> and VITE_AUTH_CLIENT_ID=<slug>.

You're then ready to make up (from the repo root) and ./scripts/smoke-test.sh from the app's directory.