Appearance
OIDC-oppsett
Denne guiden er for ops-ingeniøren som skal koble Lumi til organisasjonens OIDC-provider i en BYOC-installasjon. Etter at stegene er gjennomført kan dine interne brukere logge inn i dashboardet med samme identitet som de bruker i andre verktøy.
Provider-dekning
Keycloak er referanse-implementasjonen og er testet ende-til-ende mot repoets kode.
Auth0 og Okta-seksjonene under er kildeverifisert mot provider-dokumentasjon, men ikke E2E-verifisert mot en ekte Lumi-installasjon ennå. Behandle dem som provider-spesifikke konfigurasjonsnotater til de er validert med en testet tenant/org, et vellykket login → dashboard → API-kall, og sanerte token-samples fra access-tokenet.
Hvordan Lumi bruker OIDC
Dashboardet gjør en OAuth 2.0 Authorization Code-flyt med PKCE mot providern og lagrer token-paret i en kryptert server-side-sesjon (PostgreSQL). Per request videresender dashboardet access-tokenet (ikke id-tokenet) som Authorization: Bearer ... til Lumi-API-et. API-et validerer signatur, issuer og audience, og leser brukerens grupper fra tokenet.
Groups må være på access-tokenet, ikke bare id-tokenet
Dashboardet forward-er access-tokenet. Provideren må derfor legge gruppeverdiene på access-tokenet. For Keycloak styres dette via group-membership-mapperens access.token.claim: true-flagg; Auth0 og Okta har egne steg lenger ned.
Sentrale token-claims som Lumi leser:
| Claim | Formål | Feilmodus hvis feil |
|---|---|---|
iss | Må matche OIDC_ISSUER_URL | 401, API-loggen skriver OIDC token verification failed |
aud | Må matche OIDC_AUDIENCE | 401, samme logg-linje som over |
azp, client_id eller cid | Må matche LUMI_DASHBOARD_CLIENT_ID på endepunkter som bruker ClientAuthorizationPlugin | 403 Caller is not authorized for this endpoint |
sub | Brukerens unike ID (persisteres) | — |
email (fallback: preferred_username) | Vist i dashboardet | Tom e-postkolonne, ingen hard feil |
groups (konfigurerbar via OIDC_GROUPS_CLAIM) | Team-mapping og org-admin | Bruker får teams: [] og isOrgAdmin: false |
OidcAuthProvider i Kotlin-koden logger kun generelle verifikasjonsfeil (ikke hvilken claim som mangler) — for å finne rotårsak må du dekode tokenet lokalt og sammenligne claims mot env-varene.
Miljøvariabler
Både dashboard og API trenger sine egne sett. Sett dem før oppstart — API-et feiler startup dersom produksjon-påkrevde variabler mangler.
Dashboard
OIDC_ISSUER_URL, OIDC_CLIENT_ID, OIDC_REDIRECT_URI og LUMI_SESSION_SECRET er påkrevd når AUTH_PROVIDER=oidc. OIDC_CLIENT_SECRET er kun påkrevd for confidential clients; public clients med PKCE lar den stå tom. OIDC_SCOPES er valgfri.
sh
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://keycloak.din-bedrift.no/realms/lumi
OIDC_BROWSER_ISSUER_URL=https://keycloak.din-bedrift.no/realms/lumi # valgfri; se split-networking nedenfor
OIDC_CLIENT_ID=lumi-dashboard
OIDC_CLIENT_SECRET=<fra Keycloaks Credentials-fane> # kun confidential clients
OIDC_REDIRECT_URI=https://lumi.din-bedrift.no/auth/callback
OIDC_SCOPES=openid,profile,email # valgfri; se Entra/custom API-scope nedenfor
LUMI_SESSION_SECRET=<base64-kodet 32+ bytes>
# Session-store: samme PostgreSQL som API-et
DB_HOST=postgres.din-bedrift.no
DB_PORT=5432
DB_DATABASE=lumi
DB_USERNAME=lumi
DB_PASSWORD=<fra secret>Dashboardet lagrer OIDC-sesjonsparet (access/refresh/id-tokens) kryptert i en sessions-tabell i samme PostgreSQL som API-et. Uten DB_-variablene kan dashboardet ikke persistere sesjoner og login feiler før redirecten kommer tilbake.
OIDC_BROWSER_ISSUER_URL brukes kun når serveren når provideren på en annen URL enn nettleseren (klassisk docker-compose-situasjon der containeren når http://keycloak:8080 men brukeren må sendes til http://localhost:8180). Sett begge like i et vanlig BYOC-oppsett hvor Keycloak er tilgjengelig fra både server og nettleser på samme URL.
OIDC_SCOPES er en komma-separert liste over scopes dashboardet ber om ved login og refresh. La den stå tom for Keycloak-defaulten openid,profile,email. Providers som krever et eksplisitt API-scope i access-tokenet, typisk Entra ID eller scope-gated Okta-claims, bør settes til f.eks. openid,profile,email,api://<api-client-id>/access_as_user eller openid,profile,email,groups. openid må være med for at login fortsatt er en OIDC-flyt; API-scopet må korrespondere med OIDC_AUDIENCE som API-et validerer.
LUMI_SESSION_SECRET krypterer sesjonscookien (AES-256-GCM). Generer én gang per miljø og lagre trygt:
sh
openssl rand -base64 32Velg public eller confidential client bevisst
Keycloak-klienter finnes i to varianter: public (PKCE, ingen secret) og confidential (PKCE + secret). Lumi-dashboardet støtter begge. For produksjon anbefales vanligvis confidential client hvis provideren og driftsmodellen din støtter det; lokalt referanseoppsett i keycloak/lumi-realm.json bruker public client.
API
sh
LUMI_ENV=production
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://keycloak.din-bedrift.no/realms/lumi
OIDC_AUDIENCE=lumi-dashboard
OIDC_GROUPS_CLAIM=groups # valgfri, default "groups"
OIDC_JWKS_URI= # valgfri; se split-networking nedenfor
LUMI_DASHBOARD_CLIENT_ID=lumi-dashboard # brukes av API-et for client-identifikasjon
LUMI_ADMIN_GROUP=lumi-admins # påkrevd i produksjon (API feiler startup uten)AUTH_PROVIDER=oidc + LUMI_ENV=production er obligatorisk
Auth-modus styres av AUTH_PROVIDER — default local (statisk dev-prinsipal uten OIDC-validering). Sett AUTH_PROVIDER=oidc i produksjon, ellers slipper ingen OIDC-token gjennom validering og dashboardet snakker med et API i lokal dev-modus.
LUMI_ENV=production styrer separat strengere startup-validering (krever f.eks. LUMI_DASHBOARD_CLIENT_ID, LUMI_ADMIN_GROUP). Kjører du AUTH_PROVIDER=oidc uten LUMI_ENV=production, går API-et teknisk sett opp, men du mister guards som ville fanget manglende/feil-konfigurerte variabler før startup.
Vanlig BYOC-fallgruve — dobbeltsjekk begge variabler før go-live.
LUMI_ADMIN_GROUP er påkrevd i produksjon
Uten LUMI_ADMIN_GROUP blir enhver autentisert OIDC-bruker org-admin. API-et feiler startup dersom variabelen er tom når AUTH_PROVIDER=oidc. Se apps/lumi-api/src/main/kotlin/no/lumi/config/ServerEnv.kt.
Keycloak-oppsett
Lumi-repoet inneholder en realm-definisjon på keycloak/lumi-realm.json som er ment som utviklings- og referanse-oppsett, ikke for direkte prod-import. Den har:
- Realm
lumimed public clientlumi-dashboard - Group-membership-mapper med
full.path: falseogaccess.token.claim: true—groupssendes somlumi-admins,team-alphaetc. på både id-token og access-token - Audience-mapper på client-en slik at access-tokenets
aud = lumi-dashboard - Demo-grupper:
lumi-admins,team-alpha,team-beta - Testbrukere med hardkodede passord (
admin@lumi.test,user@lumi.test,user2@lumi.test) — derfor kan realm-filen ikke importeres direkte i prod - Public client-type som matcher lokal
.env.oidc. For prod anbefales normalt confidential client hvis provideren og driftsmodellen din støtter det.
Anbefalt: sett opp prod-realm manuelt med referanse-filen som mal
For prod skal du opprette et tomt realm og gjenbruke navnekonvensjoner og mapper-config fra lumi-realm.json, men ikke importere det direkte.
- Master realm → Create realm → navn
lumi(eller noe annet; husk å oppdatereOIDC_ISSUER_URL) - Clients → Create client:
- Client ID:
lumi-dashboard - Client type: OpenID Connect
- Anbefalt for prod: skru på Client authentication (= confidential client → gir deg en client secret). For public client lar du den stå av og utelater
OIDC_CLIENT_SECRET. - Authentication flow: kryss av Standard flow, la Direct access grants være av i prod
- Valid redirect URIs:
https://lumi.din-bedrift.no/auth/callback - Valid post logout redirect URIs:
https://lumi.din-bedrift.no/* - Web origins:
https://lumi.din-bedrift.no
- Client ID:
- Hvis client-en er confidential: Clients → lumi-dashboard → Credentials → kopier Client secret → sett som
OIDC_CLIENT_SECRETpå dashboardet - Clients → lumi-dashboard → Advanced → Proof Key for Code Exchange Code Challenge Method →
S256 - Client scopes → Create scope → navn
groups, typeDefault- Legg til Mappers → By configuration → Group Membership
- Name:
groups, Token claim name:groups - Slå av Full group path (gir
lumi-admins, ikke/lumi-admins) - Slå på Add to access token, Add to ID token, Add to userinfo
- Clients → lumi-dashboard → Client scopes → lumi-dashboard-dedicated (client-ens innebygde dedicated scope) → Add mapper → By configuration → Audience
- Name:
aud-lumi-dashboard - Included Client Audience: velg
lumi-dashboard - Add to access token: på
- Dette gir access-tokens med
aud: ["lumi-dashboard"]som matcherOIDC_AUDIENCEpå API-et. Ved å legge mapperen på dedicated scope slipper du å koble et eksternt scope til client-en.
- Name:
- Clients → lumi-dashboard → Client scopes → Add client scope → legg til
groups-scopet fra steg 5 som Default. Uten dette steget får tokenet ikke med seggroups-claimen. - Groups → Create group →
lumi-adminsog eventuelt team-grupper (f.eks.team-alpha,team-beta) - Users: legg til brukerne dine (eller koble på en identity provider som Entra ID / Google / LDAP), og assign brukere til grupper via Users → [user] → Groups → Join Group
Mappe Keycloak-grupper til Lumi-team
Keycloak-grupper kommer ikke automatisk inn som Lumi-team. Hver Keycloak-gruppe må eksplisitt kobles til et Lumi-team.
Første-gangs-oppsett oppretter admin-mapping
Når LUMI_ADMIN_GROUP er satt, oppretter /setup-wizarden automatisk en mapping fra admin-gruppen til det første default-teamet. Det gjør bootstrap-kallet og Innstillinger → Organisasjon → Team tilgjengelig umiddelbart etter setup.
Merk at mappingen også gir admin-gruppen tilgang til data i default-teamet. Hvis dere vil skille admin-tilgang fra data-tilgang, opprett et dedikert admin-team etter setup, map LUMI_ADMIN_GROUP dit og fjern mappingen fra default når ordinære team-grupper er lagt inn.
- Logg inn i dashboardet som admin. Hvis organisasjonen ikke er satt opp enda, havner du i
/setup-wizarden — fullfør den først (oppretter org + første team). - Gå til Innstillinger → Organisasjon → Team.
- For hvert team: legg til OIDC-gruppemapping hvor Group ID er det samme navnet Keycloak sender i
groups-claimen (f.eks.team-alpha, uten slash). - Lagre.
Endringer i team-mappings trer i kraft på neste request — Lumi slår opp groups → team per forespørsel mot databasen. Endringer på IdP-siden (bruker får ny gruppe i Keycloak) krever at brukeren logger ut og inn igjen for å få et nytt token med den nye group-claimen.
Alternativ: importer referanse-realmet for dev
Direkteimport er greit for lokal utvikling. Bruk docker-compose.yml i repo-roten (som allerede pinner Keycloak til versjon 26.2, bruker port 8180 for å unngå konflikt med API-ets 8080, og auto-importerer realmet):
sh
docker compose up -d keycloakSplit-networking (server-URL ≠ nettleser-URL) er avansert
Anbefalt prod-oppsett: la OIDC-provideren være tilgjengelig på én URL (typisk via ingress) for både dashboard-server, API og nettleser. Sett samme OIDC_ISSUER_URL overalt. Dette unngår all split-kompleksiteten.
Hvis split-nettverk er nødvendig (lokal compose, eller k8s uten felles ingress), brukes tre separate env-vars for ulike roller:
| Env-var | Brukt av | Rolle |
|---|---|---|
OIDC_ISSUER_URL | API | Må matche token-ens iss-claim (satt av IdP, typisk KC_HOSTNAME_URL i Keycloak) |
OIDC_ISSUER_URL | Dashboard (server) | URL dashboard-serveren bruker for server-side token-exchange — må være reachable fra dashboard-container-en |
OIDC_BROWSER_ISSUER_URL | Dashboard (browser) | URL nettleseren redirects til for login — må være reachable fra brukernes nettlesere |
OIDC_SCOPES | Dashboard | Komma-separerte scopes for login/refresh. Default er openid,profile,email; legg til API-scope for Entra/custom providers |
OIDC_JWKS_URI | API (valgfri) | Hvis JWKS-discovery via OIDC_ISSUER_URL feiler fra container-nettverket, pek direkte på container-reachable JWKS-endepunkt |
Dashboardets OIDC_ISSUER_URL og API-ets OIDC_ISSUER_URL trenger ikke være like hvis dashboard-container og API-container ser provideren på ulike URL-er, men verdien API-et validerer mot må matche det IdP-en stempler som iss.
Helm-charten eksponerer split-networking-verdiene direkte:
yaml
api:
env:
oidcIssuerUrl: https://keycloak.din-bedrift.no/realms/lumi
oidcJwksUri: http://keycloak.lumi.svc.cluster.local:8080/realms/lumi/protocol/openid-connect/certs
dashboard:
env:
oidcIssuerUrl: http://keycloak.lumi.svc.cluster.local:8080/realms/lumi
oidcBrowserIssuerUrl: https://keycloak.din-bedrift.no/realms/lumiBruk api.extraEnv eller dashboard.extraEnv for avanserte env-vars som chartet ikke modellerer eksplisitt, inkludert valueFrom-referanser til egne Secrets eller ConfigMaps.
docker-compose.yml i repo-roten viser Keycloak + Postgres-oppsettet som kjører i lokaltest, men lar OIDC-env-varene stå tomme — du må fylle dem inn manuelt per providern din. Det fungerer som et referansepunkt for nettverkstopologien (Keycloak på host-port 8180 med KC_HOSTNAME_URL=http://localhost:8180 mens API/dashboard kjører i samme docker-nettverk), ikke som en ferdig utfylt konfigurasjon.
For standalone-setup kan du kjøre:
sh
docker run -d --name lumi-keycloak -p 8180:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
-v "$(pwd)/keycloak:/opt/keycloak/data/import:ro" \
quay.io/keycloak/keycloak:26.2 \
start-dev --import-realmPort 8180 er host-porten som nettleseren treffer (KC_HOSTNAME_URL=http://localhost:8180 i compose). Containere på samme docker-nettverk når derimot Keycloak via service-navn på intern-porten (http://keycloak:8080) — localhost:8180 fra innsiden av en container er container-en selv, ikke Keycloak.
Dette er en nettverks-reachability-forskjell, ikke en issuer-identitet-forskjell: iss-claim i tokenet må fortsatt matche Keycloak sin annonserte issuer, og OIDC_ISSUER_URL på API-et (som validerer mot iss) må matche det. Splitter du issuer-URL-en mellom komponenter får du lett issuer-mismatch-feil. Lumi håndterer nettverks-forskjellen via en egen variabel for nettleser-redirects (OIDC_BROWSER_ISSUER_URL) — se "Split-networking (server-URL ≠ nettleser-URL)"-seksjonen lenger ned for den eksakte variabel-matrisen. Flagget --import-realm leser alle *.json i /opt/keycloak/data/import/ ved oppstart.
Lokal public client uten secret
Realm-filen har lumi-dashboard som public client (ingen secret), og .env.oidc i repo-roten er i samme modus. Dashboardet sender da clientSecret = null til OIDC-providerklienten og krever ikke OIDC_CLIENT_SECRET ved startup. Hvis du endrer client-en til confidential i Keycloak admin-UI, må du også sette OIDC_CLIENT_SECRET i .env.oidc.
Første-gangs-admin
Sett LUMI_ADMIN_GROUP til gruppen du er medlem av før du logger inn første gang. Uten en riktig satt admin-gruppe vil du ikke kunne administrere andre team senere. For lokalt dev-oppsett finnes LUMI_LOCAL_ADMIN=true som gjør dev-brukeren til admin — se CLAUDE.md-reglene. Dette virker kun når AUTH_PROVIDER=local.
Auth0 (kildeverifisert, ikke E2E-verifisert)
Auth0 skiller tydelig mellom OIDC-login og access-tokens til en konkret API. Lumi-API-et trenger et JWT access-token hvor aud matcher Auth0 API Identifier og hvor gruppeclaimen ligger på access-tokenet. Auth0s egne docs sier at access-token for egen API krever mål-audience/API Identifier, og at private custom claims som groups ikke bør brukes uten namespace.
Kildegrunnlag:
- Auth0: Get Access Tokens
- Auth0: Create Custom Claims
- Auth0: post-login Action API object
- Auth0: Tenant Settings, Default Audience
- Auth0: Get Refresh Tokens
Nåværende Lumi-kode sender ikke Auth0 audience i authorize-URL-en
Dashboardet har per i dag OIDC_SCOPES, men ingen egen env-var for provider-spesifikke authorize-parametre som Auth0s audience. For en dedikert Auth0-tenant kan du bruke Auth0 Tenant Settings → API Authorization Settings → Default Audience og sette den til Lumi API Identifier. Auth0 beskriver dette som ekvivalent med å legge audience på alle authorization requests i tenant-en, og advarer om at det kan endre oppførsel for andre apper.
Ikke bruk tenant-wide Default Audience i en delt Auth0-tenant uten å vurdere konsekvensene. Da bør Lumi først få eksplisitt støtte for å sende audience kun på Lumi-login.
Auth0-steg
- Applications → APIs → Create API
- Name:
Lumi API - Identifier: en URI du kontrollerer, f.eks.
https://lumi.din-bedrift.no/api - Signing Algorithm:
RS256 - For lengre dashboard-sesjoner: enable Allow Offline Access på API-et slik at Auth0 kan utstede refresh tokens når
offline_accessscopes inn. - Sett API-ets Identifier som
OIDC_AUDIENCEpå Lumi-API-et.
- Name:
- Applications → Applications → Create Application
- Type: Regular Web Application
- Allowed Callback URLs:
https://lumi.din-bedrift.no/auth/callback - Allowed Logout URLs:
https://lumi.din-bedrift.no/ - Allowed Web Origins:
https://lumi.din-bedrift.no - Sett Client ID som
OIDC_CLIENT_IDpå dashboardet ogLUMI_DASHBOARD_CLIENT_IDpå API-et. - Sett Client Secret som
OIDC_CLIENT_SECRETpå dashboardet hvis applikasjonen er confidential.
- Tenant Settings → API Authorization Settings
- For en dedikert Lumi-tenant: sett Default Audience til API Identifier fra steg 1.
- For en delt tenant: ikke gjør dette uten egen risikovurdering; se warningen over.
- Actions → Library → Build Custom → Login / Post Login
- Legg grupper på access-tokenet med en namespaced claim.
- Claim-navnet
groupser restricted i Auth0. Bruk f.eks.https://lumi.din-bedrift.no/groups, og sett samme verdi iOIDC_GROUPS_CLAIM.
- Actions → Flows → Login
- Dra Actionen inn i Login-flowen og deploy.
- Logg inn på nytt i Lumi slik at dashboardet får et nytt access-token med riktig audience og groups-claim.
Hvis offline_access ikke er aktivert og med i OIDC_SCOPES, returnerer ikke Auth0 refresh token til dashboardet. Da må brukeren logge inn på nytt når access-tokenet utløper.
Eksempel på Auth0 Action. Bruk en gruppe-/rolle-kilde som faktisk finnes i tenant-en deres; app_metadata.lumi_groups er et eksplisitt eksempel som unngår å blande personprofil med autorisasjonsdata:
js
exports.onExecutePostLogin = async (event, api) => {
const namespace = "https://lumi.din-bedrift.no";
const groups = event.user.app_metadata?.lumi_groups ?? [];
if (Array.isArray(groups)) {
api.accessToken.setCustomClaim(
`${namespace}/groups`,
groups.map(String),
);
}
};Auth0-env:
sh
# Dashboard
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://<tenant>.<region>.auth0.com/
OIDC_CLIENT_ID=<auth0-application-client-id>
OIDC_CLIENT_SECRET=<auth0-application-client-secret>
OIDC_REDIRECT_URI=https://lumi.din-bedrift.no/auth/callback
OIDC_SCOPES=openid,profile,email,offline_access
# API
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://<tenant>.<region>.auth0.com/
OIDC_AUDIENCE=https://lumi.din-bedrift.no/api
OIDC_GROUPS_CLAIM=https://lumi.din-bedrift.no/groups
LUMI_DASHBOARD_CLIENT_ID=<auth0-application-client-id>
LUMI_ADMIN_GROUP=lumi-adminsDekod access-tokenet lokalt etter login og verifiser formen, uten å lime tokenet i nettbaserte dekodere:
json
{
"iss": "https://<tenant>.<region>.auth0.com/",
"aud": [
"https://lumi.din-bedrift.no/api",
"https://<tenant>.<region>.auth0.com/userinfo"
],
"azp": "<auth0-application-client-id>",
"sub": "auth0|...",
"https://lumi.din-bedrift.no/groups": ["lumi-admins", "team-alpha"]
}Okta (kildeverifisert, ikke E2E-verifisert)
Okta kan legge grupper på access-tokenet via en custom authorization server. Bruk ikke org authorization server for Lumi-grupper: Okta-dokumentasjonen sier at org authorization server bare kan gi groups-claim på ID-tokenet, mens Lumi trenger access-tokenet.
Kildegrunnlag:
- Okta: Customize tokens with a groups claim
- Okta: Customize authorization server
- Okta: Authorization servers
- Okta: Protect your API endpoints
Okta-steg
- Security → API → Authorization Servers
- Bruk
defaultcustom authorization server eller opprett en ny authorization server. - Okta API Access Management kreves for custom authorization servers i produksjonsorgs.
- Noter issuer:
https://<okta-domain>/oauth2/<authorizationServerId>. - Noter Audience. For
defaulter dette ofteapi://default; for egen server velger du en URI som representerer Lumi-API-et. - Sett Audience som
OIDC_AUDIENCEpå Lumi-API-et.
- Bruk
- Security → API → Authorization Servers → [server] → Access Policies
- Verifiser at authorization serveren har en policy som gjelder Lumi OIDC-appen.
- Legg til en rule som tillater Authorization Code flow for Lumi-brukerne.
- Tillat scopes
openid,profile,emailoggroups. Hvis dashboardet skal kunne fornye sesjonen uten ny login, tillat ogsåoffline_accessog Refresh Token grant i samme policy/rule. - Hvis ingen policy/rule matcher requesten, feiler Okta authorization-requesten før Lumi får tokens.
- Security → API → Authorization Servers → [server] → Claims → Add Claim
- Name:
groups - Include in token type:
Access Token - Value type:
Groups - Filter: bruk en allowlist/regex som bare tar med Lumi-relevante grupper, f.eks.
^(lumi-admins|team-.+)$ - Include in:
Any scopeeller et dedikertgroupsscope hvis dere vil scope-gate claimen.
- Name:
- Applications → Applications → [Lumi OIDC app]
- Sign-in redirect URI:
https://lumi.din-bedrift.no/auth/callback - Client ID settes som
OIDC_CLIENT_IDpå dashboardet ogLUMI_DASHBOARD_CLIENT_IDpå API-et. - Client Secret settes som
OIDC_CLIENT_SECRETpå dashboardet hvis appen er confidential.
- Sign-in redirect URI:
- Hvis claimen er scope-gated: sett dashboardets
OIDC_SCOPES=openid,profile,email,groups,offline_access. Droppoffline_accesshvis dere bevisst vil ha korte dashboard-sesjoner uten refresh. - Logg inn på nytt i Lumi slik at dashboardet får et nytt access-token fra custom authorization serveren.
Okta-env:
sh
# Dashboard
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://<okta-domain>/oauth2/default
OIDC_CLIENT_ID=<okta-application-client-id>
OIDC_CLIENT_SECRET=<okta-application-client-secret>
OIDC_REDIRECT_URI=https://lumi.din-bedrift.no/auth/callback
OIDC_SCOPES=openid,profile,email,groups,offline_access
# API
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://<okta-domain>/oauth2/default
OIDC_AUDIENCE=api://default
OIDC_GROUPS_CLAIM=groups
LUMI_DASHBOARD_CLIENT_ID=<okta-application-client-id>
LUMI_ADMIN_GROUP=lumi-adminsDekod access-tokenet lokalt etter login og verifiser formen:
json
{
"iss": "https://<okta-domain>/oauth2/default",
"aud": "api://default",
"cid": "<okta-application-client-id>",
"sub": "00u...",
"groups": ["lumi-admins", "team-alpha"]
}E2E-verifisering før Auth0/Okta markeres som støttet
For hver provider må vi gjøre denne sjekklisten før #213 kan lukkes:
- Logg inn i dashboardet med en bruker som har både admin-gruppe og minst én team-gruppe.
- Bekreft at dashboardet kaller Lumi-API-et med access-tokenet fra provideren og får 200 fra bootstrap-/team-endepunkter.
- Dekod access-tokenet lokalt og lag en sanert token-sample uten ekte verdier for
sub, e-post, tenant-ID-er eller gruppe-/brukeridentifikatorer. - Bekreft at
iss,aud,azp/client_id/cid, og groups-claim matcher env-varene. - Bekreft at en bruker uten team-gruppe får 403 fra API-et, og dokumenter den faktiske responsen fra testmiljøet.
- Dokumenter faktiske feilmeldinger fra provider/API under troubleshooting.
Troubleshooting
"403 Forbidden: No teams authorized for user"
API-et returnerer 403 når brukerens groups-claim ikke matcher noen team-mapping i team_group_mappings-tabellen. Vanlige årsaker:
groupsmangler på access-tokenet — bekreft atlumi-dashboard-client-en har group-membership-mapperen aktivert med Add to access token. Dekode tokenet lokalt og verifisergroups-feltet.- Ingen team-mappings er opprettet ennå — admin må inn i dashboardets Innstillinger → Organisasjon → Team og koble OIDC-gruppe-ID til et team.
- Group-navn/ID matcher ikke — Keycloak sender
lumi-admins(uten slash) som standard; sjekk at mapping-raden bruker samme formatet. - Auth0-claimen er lagt feil sted —
api.idToken.setCustomClaim(...)hjelper ikke Lumi; brukapi.accessToken.setCustomClaim(...)og et namespaced claim-navn som matcherOIDC_GROUPS_CLAIM. - Okta bruker org authorization server — den kan gi groups på ID-token, men ikke på access-tokenet. Bruk custom authorization server og claim med Include in token type: Access Token.
- Gruppe-overflow (primært Entra-spesifikt): API-loggen skriver
OIDC group overage detected. Filtrer gruppene i providern.
Ikke lim inn access-tokens i eksterne verktøy
Tokens gir tilgang til Lumi-API-et så lenge de er gyldige (typisk 1 time). Bruk et lokalt verktøy for å dekode. Merk: unngå å lime tokenet direkte inn i shell-kommandoer — det ender i shell-historikken. Lagre heller i en midlertidig fil først, og slett etter bruk, eller bruk HISTFILE=/dev/null i en dedikert shell.
JWT bruker base64url (ikke standard base64) så konverter først:
sh
# Lagre tokenet i en temp-fil (ikke lim inn i kommandolinjen)
token_file=$(mktemp)
# ...skriv tokenet til $token_file via editor eller pbpaste...
# Bytt _/- til /+ og legg til padding iht. RFC 4648 (base64url har payload-lengde
# ≡ 0, 2, eller 3 (mod 4); 1 er ugyldig). Dekoder med -d (Linux/GNU, nyere macOS)
# og faller tilbake til -D (BSD/eldre macOS).
payload=$(cut -d. -f2 "$token_file" | tr '_-' '/+')
case $(( ${#payload} % 4 )) in
0) ;; # allerede riktig lengde
2) payload="${payload}==" ;; # mangler 2 padding-chars
3) payload="${payload}=" ;; # mangler 1 padding-char
1) echo "Ugyldig base64url-lengde i payload" >&2; exit 1 ;;
esac
(echo "$payload" | base64 -d 2>/dev/null || echo "$payload" | base64 -D) | jq .
# Rydd opp
rm "$token_file"Eller bruk jwt CLI (brew install mike-engel/jwt-cli/jwt-cli på macOS, cargo install jwt-cli cross-platform) — men pipe tokenet inn stdin for å unngå shell-history:
sh
jwt decode - < "$token_file"Aldri lim inn i nettleser-baserte dekodere eller pastebins.
"OIDC token verification failed"
API-et logger denne meldingen på DEBUG-nivå. Standardnivået er INFO, så du vil ikke se den i produksjonsloggen uten å heve nivået. Sett LUMI_API_LOG_LEVEL=DEBUG midlertidig på API-et, eller api.env.logLevel=DEBUG når du deployer via Helm.
Mulige årsaker når du ser meldingen:
audmatcher ikkeOIDC_AUDIENCE— dekod tokenet og sammenlign.- Dashboardet ber ikke om API-scopet providern krever — for Entra/custom API-er, sett
OIDC_SCOPES=openid,profile,email,api://<api-client-id>/access_as_userog logg inn på nytt. - Auth0 access-tokenet er for
/userinfo, ikke Lumi-API-et — sett Auth0 Default Audience i en dedikert tenant, eller legg til kode-støtte for Auth0audiencefør delt-tenant-oppsett. - Okta issuer er org authorization server (
https://<domain>) i stedet for custom authorization server (https://<domain>/oauth2/<id>) — bruk custom issuer og settOIDC_AUDIENCEtil authorization serverens Audience. issmatcher ikkeOIDC_ISSUER_URL— typisk trailing slash eller feil realm-navn.- JWKS URL-discovery fungerer (gjøres ved API-startup mot
OIDC_ISSUER_URL/.well-known/openid-configuration), men selve nøkkel-hentingen skjer ved første token-validering. Hvis nettverket til providern er blokkert fra API-pod-en vil valideringen feile. Sjekk egress-regler. - Token utløpt — refresh-logikken i dashboardet skal dekke dette, men hvis klokken på server/klient driver mye kan tokens feile prematurt.
"isOrgAdmin = false selv om jeg er i admin-gruppen"
- Bekreft at
LUMI_ADMIN_GROUPmatcher eksakt formatet providern sender. For vårt realm-oppsett:lumi-adminsuten ledende slash (full.path: false). - Restart API-en —
LUMI_ADMIN_GROUPleses ved oppstart. - Logg ut + inn igjen i dashboardet for å få nytt token.
"Login fungerer lokalt men ikke i prod"
Redirect URI må matche det som er registrert i providern eksakt, inkludert protokoll, port og trailing slash. Åpne provider-siden og sjekk registrerte redirect URIs mot hva dashboardet sender (se Location-headeren i devtools når du klikker "Logg inn").
"API feiler startup: LUMI_ADMIN_GROUP is required"
Forventet når AUTH_PROVIDER=oidc og LUMI_ADMIN_GROUP er tom. Dette er håndhevet for å unngå utilsiktet full tilgang til alle autentiserte brukere. Sett env-varen før restart.
"LUMI_SESSION_SECRET is required"
Dashboardet starter ikke. Generer en med openssl rand -base64 32 og sett verdien. For rotering kan flere komma-separerte verdier settes samtidig — eldre sesjoner fortsetter å fungere til de utløper.
Se også
- Produksjonsherdning — API-nøkler, rate-limiting, image-pinning
- Oppgradering — migrasjonskompatibilitet ved nye versjoner
- Distribuer Lumi — installasjon og oppsett
