Auth-flow

Dyb gennemgang af hvordan Ressourcify autentificerer og autoriserer brugere via Microsoft Entra ID.

Ressourcify bruger NextAuth v5 med Microsoft Entra ID som eneste identitets-provider. Sessioner er stateless via JWT — der er ingen session-tabel i databasen.

De fire deltagere

KomponentRolle
BrowserSender request, opbevarer JWT-cookie
Next.jsValiderer, kalder Entra, læser DB
Microsoft Entra IDIdentitets-provider, udsteder ID-tokens
PostgresKilde til brugere og roller

Komplet flow ved første login

De tre callbacks

signIn — kører ved hvert login

Ansvarlig for at oprette/opdatere brugeren og synkronisere roller fra Entra-grupper.

async signIn({ user, profile }) {
  if (!profile?.oid || !user.email) return false
 
  // Gate-keeper: brugeren skal være i users-gruppen
  const groups = profile.groups ?? []
  if (entraConfig.groupUsersId && !groups.includes(entraConfig.groupUsersId)) {
    return false  // ← afviser login
  }
 
  // Upsert bruger på baggrund af azureAdOid (stabil)
  const dbUser = await db.user.upsert({
    where: { azureAdOid: profile.oid },
    update: { name: user.name, email: user.email },
    create: { /* … */ },
  })
 
  // Alle får RESOURCE som baseline
  await db.userRole.upsert({ /* RESOURCE */ })
 
  // Map hver mappet gruppe til en rolle
  for (const { groupId, role } of groupRoleMap) {
    if (groups.includes(groupId)) {
      await db.userRole.upsert({ /* role */ })
    }
  }
 
  return true
}

Vi bruger profile.oid som stabil identifier — ikke email. Email kan ændres i Entra, men oid er forevigt fastsat ved oprettelse af kontoen.

jwt — kører ved hvert request der har en JWT

Holder JWT-payload'en minimal — kun oid så vi kan slå brugeren op senere.

async jwt({ token, profile }) {
  if (profile?.oid) token.oid = profile.oid
  return token
}

JWT'en lever i cookien, ikke i databasen. Den er signeret med AUTH_SECRET (HMAC). Hvis cookien manipuleres, fejler signaturen og NextAuth afviser den.

session — kører hver gang auth() kaldes i en handler

Beriger session-objektet med fersk data fra databasen.

async session({ session, token }) {
  if (!token.oid) return session
  const dbUser = await db.user.findUnique({
    where: { azureAdOid: token.oid },
    select: {
      id: true,
      organizationId: true,
      primaryDepartmentId: true,
      isManager: true,
      roles: { select: { role, scopeType, scopeId } },
      organization: { select: { name: true } },
    },
  })
  if (dbUser) Object.assign(session.user, dbUser)
  return session
}

Rolle-ændringer træder i kraft ved næste session-callback — det er ofte få sekunder, ikke ved næste login. Gruppe-ændringer i Entra kræver derimod nyt login, fordi signIn kun læser grupperne én gang.

Session-konfiguration

IndstillingVærdiHvorfor
strategy"jwt"Stateless — ingen session-tabel
maxAge60 * 60 * 12 (12t)Én arbejdsdag
Cookie-navn__Secure-next-auth.session-tokenSecure-flag i produktion
Cookie-attrHttpOnly, SameSite=LaxXSS- og CSRF-modstand

Re-login efter de 12 timer går silent via Entra hvis brugeren stadig er logget ind på Microsoft — typisk usynligt for brugeren.

Adgangskontrol efter login

Den autentificerede session er kun det første lag. Hver route-handler laver yderligere et can()-tjek:

Se RBAC-matrix for hvem der må hvad.

Konfiguration

Entra-config læses ved hver kald via getEntraConfig(orgId). Først tjekkes DB (EntraConfig-tabel) — ellers falder den tilbage til miljøvariable:

Env-variabelDB-feltBeskrivelse
AUTH_MICROSOFT_ENTRA_ID_TENANT_IDtenantId
AUTH_MICROSOFT_ENTRA_ID_IDclientIdApp-registreringens client-ID
AUTH_MICROSOFT_ENTRA_ID_SECRETclientSecretEncryptedKrypteret med ENCRYPTION_KEY
ENTRA_GROUP_USERSgroupUsersIdAdgangs-gate
ENTRA_GROUP_ORG_ADMINgroupOrgAdminId
ENTRA_GROUP_DEPT_ADMINgroupDeptAdminId
ENTRA_GROUP_COORDINATORgroupCoordinatorId

DB-versionen er den foretrukne for produktion — clientSecret ligger aldrig i klartekst, og admins kan rotere den via UI.

Sikkerhedsegenskaber

EgenskabHvordan
Single sign-onEntra håndterer credentials — Ressourcify ser dem aldrig
Lockout, MFAKonfigureret i Entra
Replay-modstandOIDC-nonce valideres af NextAuth
Token-manipulationJWT signeres med AUTH_SECRET; modificerede tokens afvises
Brute-forceLogin-endpoint findes ikke i Ressourcify — al login går via Entra
Privilegium-eskaleringsignIn skriver kun ORG-scope-roller; DEPT/TEAM kræver manuel admin-handling

Hvad sker når brugeren logger ud

Klik på navn → Log ud:

  1. Browser sender POST /api/auth/signout
  2. NextAuth sletter session-cookien
  3. Audit-event LOGOUT skrives
  4. Browser redirectes til login-siden

Bemærk: brugeren er stadig logget ind på Microsoft. Hvis de besøger appen igen, går de tilbage via silent SSO. For at logge ud af Microsoft helt skal de gå til login.microsoftonline.com.