Gå til hovedinnhold

Agentnativ autentisering

fremforge godtar tre autentiseringsmønstre for OAuth2 / OIDC for kallere som ikke er mennesker (agenter, skript, AI-assistenter, CI-runnere, leverandørintegrasjoner). Alle tre bruker det samme maskineriet med tillatelseslister for scopes per kunde og revisjonskjede som innlogging for brukere – ingen skyggevei, ingen egen tillitsbutikk.

Den felles forutsetningen for alle flyter som involverer kundens IdP: registrer IdP-en din som en OIDC-autentiseringskilde under Organisasjonsadministrasjon → SSO → Autentiseringskilder, bekreft domenet, og konfigurer tillatelseslister for scopes per app på redigeringssiden for autentiseringskilden under Exchangeable scopes. Den samme registreringen driver føderert innlogging for brukere, så en kunde som bruker OIDC-SSO, har allerede grunnlaget på plass.

Implementasjonsmerknad: Endepunktene /_app/api/v1/auth/token-exchange og /_app/api/v1/oauth/token betjenes av fremforges kontrollplan – de er ikke Forgejos egne endepunkter. SDK-forfattere bør bruke https://frem.sh/_app/api/v1/* nøyaktig som dokumentert.

De tre flytene i korte trekk

FlytSpesifikasjonRollen til kundens IdPPasser best for
1. Direkte JWT-bearer– (uformelt, «Bearer JWT»)Utsteder JWT-en; fremforge verifisererArbeidslastidentiteter (Entra workload identity, Okta-tjenestekonto, Google-tjenestekonto) som allerede utsteder JWT-er bundet til kunden
2. Token exchange (RFC 8693)RFC 8693Utsteder subject_token (+ valgfri actor_token for OBO)CI-workflower, planlagte agenter, on-behalf-of-flyter (OBO) der revisjonen må navngi personen og agenten som handler for vedkommende
3. client_credentials (RFC 6749 §4.4)RFC 6749Ingen – fremforge utsteder både client_id og client_secretApper som ikke har noen IdP i det hele tatt, eller der kunden foretrekker fremforge som IdP

Alle tre flytene gir et kortlivet (≤ 1 time) ffp_-tilgangstoken bundet til én kunde og ett sett med scopes. RBAC, frekvensbegrensning, revisjonshendelser og sperrer for isolasjon mellom kunder er identiske med en PAT utstedt av et menneske.

Flyt 1 – JWT fra kundens IdP direkte som bearer

Agenten presenterer JWT-en fra IdP-en direkte som Authorization: Bearer <jwt> på et hvilket som helst /api/v1/*-endepunkt. fremforge verifiserer JWT-en mot den registrerte autentiseringskilden din, bruker snittet av scopes fra kildens app-tildeling og returnerer svaret – ingen ekstra rundtur, ingen lagret legitimasjon.

Endepunkt: en hvilken som helst frem.sh/_app/api/v1/*-rute.

Oppsett (én gang, per autentiseringskilde):

  1. Organisasjonsadministrasjon → SSO → registrer en OIDC-kilde for IdP-en din (eller gjenbruk en eksisterende som brukes til innlogging for brukere).
  2. Konfigurer tillatelseslister for scopes per app under Edit auth source → App grants. Tillatelseslisten binder en IdP-applikasjon (matchet på JWT-ens claim azp eller aud[0]) til et maksimalt sett med scopes.
  3. Organisasjonsadministrasjon → Programmatisk tilgang → Administrer JWT-bearer + bytte → slå på Tillat direkte JWT-bearer for kilden.
  4. (Bare IdP-er med flere kunder) Skriv inn en claim assertion som binder kilden til din kunde – {"tid": "<your-Entra-tenant-guid>"} for Entra /common/, {"hd": "yourdomain.com"} for Google Workspace osv. Administrasjonsskjemaet nekter å slå på direkte bearer for en utsteder-URL med flere kunder uten claim assertions.

Krav til tokenet:

  • iss samsvarer med den registrerte kilde-URL-en (normalisert uten hensyn til store og små bokstaver, tolererer avsluttende skråstrek).
  • aud samsvarer enten med kildens konfigurerte Audience override ELLER med den kanoniske målgruppen per kunde, https://frem.sh/<your-tenant-slug>.
  • exp ligger i framtiden, iat ligger ikke urimelig langt tilbake (jose håndhever 30 sekunders klokkeavvik).
  • Alle claim_assertions som er konfigurert på kilden, samsvarer med den verifiserte nyttelasten.
  • Signeringsnøkkelen løses opp via OIDC discovery + JWKS (bufret i 10 minutter, kan styres av kunden gjennom vanlig nøkkelrotasjon i IdP-en).

Eksempel (Entra workload identity):

# 1. Acquire a JWT from your IdP (managed identity, federated identity,
#    or workload-identity SDK) with aud=https://frem.sh/<your-slug>
ACCESS_TOKEN=$(az account get-access-token \
  --resource https://frem.sh/acme \
  --query accessToken -o tsv)

# 2. Call any fremforge API directly
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  https://frem.sh/_app/api/v1/orgs/acme/repos

Sikkerhetsegenskaper:

  • JWT-en er legitimasjonen. Ingen PAT-rad opprettes i fremforges database. Tilbakekalling = la exp løpe ut, ELLER fjern IdP-kilden / slå av direkte bearer (neste forespørsel avvises).
  • Binding på tvers av kunder er umulig av konstruksjon: en målgruppe per kunde binder JWT-en til én slug, og tabellen auth_sources håndhever en UNIQUE-begrensning på databasenivå som hindrer to kunder i å registrere samme utsteder for direkte bearer.
  • Øvre grense for scopes fra kildens tildeling per app. Scopes som flytter penger (billing:write, sso:write, policy:write, orgs:write), krever eksplisitte oppføringer i tillatelseslisten per app – de gis ALDRI av en tillatelsesliste som er NULL.

Flyt 2 – RFC 8693 token exchange (med OBO)

Agenten presenterer en JWT fra IdP-en som subject_token og bytter den mot en kortlivet fremforge-PAT (ffp_). Parameteren actor_token muliggjør on-behalf-of-delegering – agenten handler for en navngitt person. fremforge har ingen agentidentiteter: personen er aktøren i revisjonsloggen, og agenten er en detalj i hver revisjonsrad.

Endepunkt: POST frem.sh/_app/api/v1/auth/token-exchange.

Eksempel (enkel exchange):

curl -X POST https://frem.sh/_app/api/v1/auth/token-exchange \
  -H "Content-Type: application/json" \
  -d '{
    "subject_token": "<customer-idp-jwt>",
    "tenant_slug": "acme",
    "scopes": ["orgs:read", "findings:write"],
    "name": "ci-build-bot"
  }'

Eksempel (OBO – agenten handler på vegne av Alice):

curl -X POST https://frem.sh/_app/api/v1/auth/token-exchange \
  -H "Content-Type: application/json" \
  -d '{
    "subject_token": "<alice-idp-jwt>",
    "actor_token":   "<agent-idp-jwt>",
    "tenant_slug": "acme",
    "scopes": ["findings:read", "audit:read"]
  }'

Be bare om scopes som token exchange kan gi: settet som kan byttes (orgs:read, orgs:write, audit:read, exports:read, exports:write, findings:read, findings:write, components:read, deployments:report, runners:read, runners:write, sboms:write, seats:read, seats:write, billing:read, billing:write, sso:read, sso:write, policy:read, policy:write), innsnevret av app-tildelingen på autentiseringskilden din. Et scope utenfor ett av dem droppes, og en forespørsel uten noe igjen avvises med no-scopes-grantable. Tilgang til repositoryer og issues er ikke med i fremforge-API-ets sett med scopes, så det kan ikke byttes.

Hver revisjonshendelse som den utstedte PAT-en skriver, navngir Alice som actor, som oidc:idp:<alice-sub> (den verifiserte sub fra subject-tokenet). Agenten er en detalj i raden: fields.agent er oidc:agent:<agent-sub> (den verifiserte sub fra actor-tokenet), og fields.via_token er id-en til den utstedte PAT-en. Selve byttet registreres som api.token.exchange.obo, også med Alice som aktør og agenten i fields.agent. Begge verdiene kommer bare fra de verifiserte JWT-ene; ingenting i forespørselens body kan navngi eller endre personen.

Uten en actor_token navngir et bytte med kundens IdP likevel personen som aktør, med via_token og uten agent. Et bytte med runner-OIDC har ingen person (en CI-jobb er ikke en person), så hendelsene beholder actor=token:<id>.

Samsvarsspørringer som «alle handlinger Anthropic-agenten min utførte på vegne av Alice forrige kvartal» filtrerer revisjonsloggen på Alice og leser fields.agent. Aktørfilteret matcher aktøren, kolonnen on_behalf_of og fields.agent, så et filter på oidc:agent:<agent-sub> finner alt den agenten gjorde, for alle personer. I revisjonsloggen i administrasjonen matcher filteret en delstreng, og slike rader har merket via agent; GET /orgs/{org}/audit?actor= matcher eksakt. Hendelser skrevet før 2026-10-23 navnga agenten som aktør og Alice i on_behalf_of. Hashkjeden dekker dem, så de beholder den formen, vises som on behalf of Alice, og de samme filtrene finner dem.

Forskjell fra flyt 1: token exchange oppretter en lagret PAT (så den kan ha et eksplisitt delsett av scopes og et eget navn). Flyt 1 er flyktig. Velg exchange når agenten skal gjøre mange kall i én workflow-kjøring; velg direkte bearer når hvert kall er uavhengig.

Flyt 3 – client_credentials (fremforge som IdP)

For apper uten IdP, eller der kunden foretrekker ett sett med legitimasjon som administreres i fremforge. Organisasjonsadministratoren utsteder et par (client_id, client_secret), appen autentiserer med Authorization: Basic (eller POST-skjema) og får en kortlivet ffp_-PAT.

Oppsett:

  1. Organisasjonsadministrasjon → Programmatisk tilgang → Administrer JWT-bearer + bytte → Opprett ny klient (under avsnittet client_credentials).
  2. Velg et navn, en øvre grense for scopes og en valgfri tillatelsesliste for målgrupper.
  3. Kopier client_secret med en gang – den vises én gang og aldri igjen.

Endepunkt: POST frem.sh/_app/api/v1/oauth/token.

Eksempel:

curl -X POST https://frem.sh/_app/api/v1/oauth/token \
  -u "ffc_<client-id>:ffs_<client-secret>" \
  -d "grant_type=client_credentials" \
  -d "scope=findings:write runners:read"

Svar:

{
  "access_token": "ffp_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "findings:write runners:read",
  "audience": "https://frem.sh/acme"
}

Rotasjon: trykk når som helst på knappen Roter i administrasjonsgrensesnittet. Den nåværende secreten forblir gyldig sammen med den nye i 24 timer, så du kan rulle ut konfigurasjonen uten nedetid. Etter 24-timersvinduet autentiserer bare den nye secreten.

Tilbakekalling: et klikk på Tilbakekall ugyldiggjør klienten OG tilbakekaller i kaskade hvert ffp_-token den noen gang har utstedt, i én transaksjon – feilsikkert selv om operatørpoden drepes midt i tilbakekallingen.

Hva de tre flytene har felles

EgenskapSlik håndheves den
Isolasjon mellom kunderHvert utstedt token har tenant_id satt; requireTenantBinding avviser kall på tvers av kunder med 404. UNIQUE på databasenivå hindrer at én utsteder gjøres krav på av to kunder for direkte bearer.
Øvre grense for scopesAlle tre tar snittet mot et operatørstyrt sett av scopes som kan byttes OG kundens tillatelsesliste per IdP / per klient. Scopes som flytter penger (billing:write, sso:write, policy:write, orgs:write), krever eksplisitte oppføringer i tillatelseslisten – de gis aldri som standard.
TTLAlle utstedte tokener har en grense på 1 time. Kunder som vil ha legitimasjon med lengre levetid, bruker utstedelsen i administrasjonsgrensesnittet (personlige tilgangstokener).
RevisjonskjedeHver utstedelse skriver en audit_event med actor, handling og scopes. For et bytte med kundens IdP er aktøren personen, og agenten (for OBO) er fields.agent; hver senere hendelse tokenet skriver, har personen som aktør pluss agent og via_token. Kjedens hash dekker fields_json, inkludert begge.
FrekvensbegrensningToken-endepunktene er frekvensbegrenset per IP. /auth/token-exchange og /oauth/token deler en grense på 30/min for å begrense CPU-kostnaden ved JWT-verifisering av falske inndata.
Nåbarhet for JWKSAlle utgående henting av JWKS og discovery går gjennom en begrenset utgående gateway – SSRF, private IP-er og skymetadata blokkeres i nettverksleddet. JWKS bufres i 10 min.
EU-lokaliseringKjeden når aldri et USA-hostet Sigstore-, Auth0- eller Okta-endepunkt – verifiseringen skjer helt innenfor fremforges eu-de-fotavtrykk i T Cloud Public. Utgående trafikk til kundens IdP går bare gjennom SSRF-proxyet henting.

Sammendrag av trusselmodellen

TrusselTiltak
Kunden registrerer en Entra /common/-URL med flere kunder uten claim_assertion for tidAdministrasjonsskjemaet nekter; hjelpefunksjonen isMultiTenantIssuerUrl() flagger Entra (v1 + v2), Google Workspace og Microsofts Gov- / Kina-skyer
To kunder deler en iss-URL for IdPDelvis UNIQUE-indeks på (LOWER(RTRIM(iss,'/'))) WHERE direkte bearer er slått på – den andre registreringen avvises i databaselaget
JWT fra kundens IdP spilles av mot feil kundeMålgruppe per kunde, https://frem.sh/<slug> – et token utstedt for aud=https://frem.sh/acme kan bokstavelig talt ikke bestå målgruppesjekken til kunden initech
Lekket client_secretOrganisasjonsadministratoren kjører Revoke → en kaskadetransaksjon fjerner klienten OG hver PAT den har utstedt; rotasjon støtter et 24-timers vindu med to secrets for bytte uten nedetid
Forfalskede claims i OBO-actor_tokenjose verifiserer signatur + iss + aud mot den samme kilden som subject; sub er uforanderlig, email/name stoles aldri på
JWKS-bufferen er utdatert etter nøkkelrotasjon i IdP-enFast TTL på 10 min – driftsmessig avgrenset
Internt vertsnavn i IdP-ens discovery-dokument (DNS rebind / SSRF)Den begrensede utgående gatewayen blokkerer hentingen; en https://-sjekk i koden gir dybdeforsvar

Hurtigreferanse

StiMetodeAutentiseringTTLBody
/_app/api/v1/auth/token-exchangePOSTJWT-en ER autentiseringen1tsubject_token (+ valgfri actor_token), tenant_slug, scopes, name
/_app/api/v1/oauth/tokenPOSTBasic ELLER skjema i body1tgrant_type=client_credentials, client_id, client_secret, scope, audience
/api/v1/* (alle)alleAuthorization: Bearer <jwt or ffp_*>etter exp / etter PAT-raden–

Relatert

  • SSO-oppsett (OIDC) – registrer IdP-en din både for innlogging for brukere og for agentautentisering
  • Workflow-OIDC for skyautentisering – den utgående retningen (runner-VM → tillitspolicyer i AWS/GCP/Azure)
  • Revisjonslogg + kjede – verifiser hva hver agent gjorde, med filter på personen eller på agenten
  • API-tokener (PAT-er) – langlivede tokener utstedt av mennesker for den samme /api/v1/*-flaten; utstedes under Brukerinnstillinger → API-tokener, se Autentiseringspolicy for maksimal levetid og rotasjonspolicy