Agent-native autentificering
fremforge accepterer tre mønstre for OAuth2/OIDC-autentificering af ikke-menneskelige kaldere (agenter, scripts, AI-assistenter, CI-runners, integrationer fra leverandører). Alle tre bygger på den samme scope-tilladelsesliste pr. tenant og den samme revisionskæde som brugerlogin: ingen skyggevej og intet separat tillidslager.
Den fælles forudsætning for alle flows, der involverer kundens IdP: registrér din IdP som en OIDC-autentificeringskilde under Administration → SSO → Autentificeringskilder, verificér domænet, og konfigurér scope-tilladelseslister pr. app på kildens redigeringsside under Exchangeable scopes. Den samme registrering driver føderation af menneskeligt login, så en kunde, der bruger OIDC-SSO, allerede har fundamentet på plads.
Note om implementeringen: Endpointene
/_app/api/v1/auth/token-exchangeog/_app/api/v1/oauth/tokenleveres af fremforges kontrolplan. De er ikke indbyggede Forgejo-endpoints. SDK-udviklere skal rammehttps://frem.sh/_app/api/v1/*præcis som dokumenteret.
De tre flows i overblik
| Flow | Specifikation | Kundens IdP’s rolle | Bedst til |
|---|---|---|---|
| 1. Direkte JWT-bearer | — (uformel, “Bearer JWT”) | Udsteder JWT’en; fremforge verificerer | Workload-identiteter (Entra workload identity, Okta-servicekonto, Google-servicekonto), der allerede udsteder tenant-bundne JWT’er |
| 2. Tokenudveksling (RFC 8693) | RFC 8693 | Udsteder subject_token (+ valgfrit actor_token til OBO) | CI-workflows, planlagte agenter og on-behalf-of-flows (OBO), hvor revisionen skal navngive personen og den agent, der handler for vedkommende |
3. client_credentials (RFC 6749 §4.4) | RFC 6749 | Ingen; fremforge udsteder både client_id og client_secret | Apps, der slet ikke har en IdP, eller hvor kunden foretrækker fremforge som IdP |
Alle tre flows giver et kortlivet (≤ 1 time) ffp_-adgangstoken, der er bundet til én tenant og ét sæt scopes. RBAC, rate limiting, skrivning til revisionsloggen og tenant-isolering fungerer præcis som for en PAT, et menneske har oprettet.
Flow 1: Direkte JWT fra kundens IdP som bearer
Agenten sender sin IdP-udstedte JWT direkte som Authorization: Bearer <jwt> til et hvilket som helst /api/v1/*-endpoint. fremforge verificerer JWT’en mod din registrerede autentificeringskilde, anvender fællesmængden af scopes fra kildens app-tildeling og returnerer svaret: ingen ekstra rundtur og ingen gemt legitimationsoplysning.
Endpoint: enhver frem.sh/_app/api/v1/*-route.
Opsætning (én gang pr. autentificeringskilde):
- Administration → SSO → registrér en OIDC-kilde for din IdP (eller genbrug en, der allerede bruges til menneskeligt login).
- Konfigurér scope-tilladelseslister pr. app under Edit auth source → App grants. Tilladelseslisten binder en IdP-applikation (matchet på JWT’ens
azp- elleraud[0]-claim) til et maksimalt sæt scopes. - Administration → Programmatisk adgang → Administrér JWT-bearer og udveksling → slå Tillad direkte JWT-bearer til på kilden.
- (Kun multi-tenant-IdP’er) Indtast en claim-assertion, der binder kilden til din tenant:
{"tid": "<your-Entra-tenant-guid>"}for Entra/common/,{"hd": "yourdomain.com"}for Google Workspace osv. Administrationsformularen nægter at slå direkte bearer til på en multi-tenant-udsteder-URL uden claim-assertions.
Krav til tokenet:
issmatcher den registrerede kilde-URL (normaliseret uden hensyn til store og små bogstaver og med tolerance for en afsluttende skråstreg).audmatcher enten kildens konfigurerede audience-tilsidesættelse ELLER den kanoniske audience pr. tenanthttps://frem.sh/<your-tenant-slug>.expligger i fremtiden, ogiatligger ikke urimeligt langt tilbage (jose håndhæver 30 sekunders tidsforskydning).- Alle
claim_assertions, der er konfigureret på kilden, matcher den verificerede payload. - Signeringsnøglen findes via OIDC discovery + JWKS (cachet i 10 minutter og styret af kunden gennem IdP’ens normale nøglerotation).
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/reposSikkerhedsegenskaber:
- JWT’en er legitimationsoplysningen. Der oprettes ingen PAT-række i fremforges database. Tilbagekaldelse = lad
expudløbe, ELLER fjern IdP-kilden / slå direkte bearer fra (næste request afvises). - Binding på tværs af tenants er umulig i kraft af designet: en audience pr. tenant binder JWT’en til én slug, og tabellen auth_sources har en UNIQUE-begrænsning på databaseniveau, der forhindrer to tenants i at registrere den samme udsteder til direkte bearer.
- Loftet over scopes kommer fra kildens app-tildeling. Scopes, der flytter penge (
billing:write,sso:write,policy:write,orgs:write), kræver udtrykkelige poster i tilladelseslisten pr. app. De gives ALDRIG af en NULL-tilladelsesliste.
Flow 2: Tokenudveksling efter RFC 8693 (med OBO)
Agenten sender en IdP-udstedt JWT som subject_token og udveksler den til en kortlivet fremforge-PAT (ffp_). Parameteren actor_token giver on-behalf-of-delegering: agenten handler for en navngiven person. fremforge har ingen agentidentiteter: personen er aktøren i revisionsloggen, og agenten er en detalje i hver række.
Endpoint: POST frem.sh/_app/api/v1/auth/token-exchange.
Eksempel (almindelig udveksling):
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 af 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"]
}'Bed kun om scopes, som tokenudveksling kan give: det udvekslelige sæt (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), indsnævret af app-tildelingen på din autentificeringskilde. Et scope uden for et af dem droppes, og en request, hvor der intet er tilbage, afvises med no-scopes-grantable. Adgang til repositorier og issues er ikke en del af fremforge-API’ets scopes og kan derfor ikke udveksles.
Hver revisionshændelse, som den udstedte PAT skriver, navngiver Alice som actor, som oidc:idp:<alice-sub> (den verificerede sub fra subject-tokenet). Agenten er en detalje i rækken: fields.agent er oidc:agent:<agent-sub> (den verificerede sub fra actor-tokenet), og fields.via_token er id’et på den udstedte PAT. Selve udvekslingen registreres som api.token.exchange.obo, også med Alice som aktør og agenten i fields.agent. Begge værdier kommer udelukkende fra de verificerede JWT’er; intet i request-body’en kan navngive eller ændre personen.
Uden et actor_token navngiver en udveksling med kundens IdP stadig personen som aktør, med via_token og uden agent. En udveksling med runner-OIDC har ingen person (et CI-job er ikke en person), så dens hændelser beholder actor=token:<id>.
Compliance-forespørgsler som “alle handlinger, min Anthropic-agent udførte på vegne af Alice i sidste kvartal” filtrerer revisionsloggen på Alice og læser fields.agent. Aktørfilteret matcher aktøren, kolonnen on_behalf_of og fields.agent, så et filter på oidc:agent:<agent-sub> finder alt, hvad den agent har gjort, for enhver person. I administrationens revisionslog matcher filteret en delstreng, og sådanne rækker har en via agent-chip; GET /orgs/{org}/audit?actor= matcher præcist. Hændelser skrevet før 2026-10-23 navngav agenten som aktør og Alice i on_behalf_of. Hashkæden dækker dem, så de beholder den form, vises som på vegne af Alice, og de samme filtre finder dem.
Forskellen fra flow 1: tokenudveksling opretter en gemt PAT (så den kan have en udtrykkelig delmængde af scopes og et selvvalgt navn). Flow 1 er flygtigt. Vælg udveksling, når agenten skal foretage mange kald i løbet af én workflow-kørsel; vælg direkte bearer, når hvert kald er uafhængigt.
Flow 3: client_credentials (fremforge som IdP)
Til apps uden en IdP, eller hvor kunden foretrækker ét sæt legitimationsoplysninger, der administreres i fremforge. Tenant-administratoren opretter et (client_id, client_secret)-par, appen autentificerer sig med Authorization: Basic (eller en POST-formular) og modtager en kortlivet ffp_-PAT.
Opsætning:
- Administration → Programmatisk adgang → Administrér JWT-bearer og udveksling → Opret ny klient (under afsnittet
client_credentials). - Vælg et navn, et loft over scopes og eventuelt en tilladelsesliste for audience.
- Kopiér
client_secretmed det samme: den vises én gang og aldrig igen.
Endpoint: 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"
}Rotation: tryk når som helst på knappen Rotér i administrationen. Den nuværende secret forbliver gyldig sammen med den nye i 24 timer, så du kan skifte din konfiguration uden nedetid. Efter de 24 timer autentificerer kun den nye secret.
Tilbagekaldelse: et klik på Tilbagekald gør klienten ugyldig OG tilbagekalder i samme transaktion alle ffp_-tokens, den nogensinde har udstedt. Det er fejlsikkert, selv hvis operatørens pod bliver dræbt midt i tilbagekaldelsen.
Hvad de tre flows har til fælles
| Egenskab | Sådan håndhæves den |
|---|---|
| Tenant-isolering | Hvert udstedt token har tenant_id sat; requireTenantBinding afviser kald på tværs af tenants med 404. En UNIQUE-begrænsning på databaseniveau forhindrer, at to tenants gør krav på samme udsteder til direkte bearer. |
| Loft over scopes | Alle tre tager fællesmængden med et udvekslingsbart sæt af scopes, som operatøren styrer, OG kundens tilladelsesliste pr. IdP / pr. klient. Scopes, der flytter penge (billing:write, sso:write, policy:write, orgs:write), kræver udtrykkelige poster i tilladelseslisten og gives aldrig som standard. |
| TTL | Alle udstedte tokens har et loft på 1 time. Kunder, der ønsker legitimationsoplysninger med længere levetid, bruger udstedelsen i administrationen (personlige adgangstokens). |
| Revisionskæde | Hver udstedelse skriver en audit_event med actor, handling og scopes. Ved en udveksling med kundens IdP er aktøren personen, og agenten (ved OBO) står i fields.agent; hver senere hændelse, tokenet skriver, har personen som aktør plus agent og via_token. Kædens hash dækker fields_json, inklusive begge. |
| Rate limiting | Token-endpointene er rate-begrænset pr. IP. /auth/token-exchange og /oauth/token deler et loft på 30/min for at begrænse CPU-omkostningen ved JWT-verificering af falske input. |
| Adgang til JWKS | Alle udgående hentninger af JWKS og discovery går gennem en begrænset egress-gateway: SSRF, private IP’er og cloud-metadata blokeres på netværksniveau. JWKS caches i 10 min. |
| EU-residens | Pipelinen rammer aldrig et Sigstore-, Auth0- eller Okta-endpoint i USA; verificeringen foregår udelukkende inden for fremforges T Cloud Public eu-de-fodaftryk. Egress til kundens IdP går kun gennem den SSRF-beskyttede proxy. |
Trusselsmodel kort fortalt
| Trussel | Afbødning |
|---|---|
Kunden registrerer en multi-tenant Entra /common/-URL uden en tid-claim_assertion | Administrationsformularen afviser; hjælpefunktionen isMultiTenantIssuerUrl() markerer Entra (v1 + v2), Google Workspace og Microsofts Gov-/Kina-clouds |
To tenants deler en IdP-iss-URL | Delvist UNIQUE-indeks på (LOWER(RTRIM(iss,'/'))) WHERE direkte bearer er slået til; den anden registrering afvises på databaseniveau |
| En JWT fra kundens IdP genbruges mod den forkerte tenant | Audience pr. tenant https://frem.sh/<slug>: et token udstedt til aud=https://frem.sh/acme kan bogstaveligt talt ikke opfylde tjekket af tenanten initechs audience |
Lækket client_secret | Tenant-administratoren kører Tilbagekald → én transaktion dræber klienten OG hver PAT, den har udstedt; rotation giver et vindue på 24 timer med to secrets, så skiftet kan ske uden nedetid |
| Forfalskede claims i OBO-actor_token | jose verificerer signatur + iss + aud mod den samme kilde som subject; sub kan ikke ændres, og email/name stoles der aldrig på |
| Forældet JWKS-cache efter nøglerotation hos IdP’en | Fast TTL på 10 min: begrænset i drift |
| Netværksinternt værtsnavn i IdP’ens discovery-dokument (DNS rebind / SSRF) | Den begrænsede egress-gateway blokerer hentningen; et https://-værn i koden giver et ekstra forsvarslag |
Hurtigt opslag
| Sti | Metode | Autentificering | TTL | Body |
|---|---|---|---|---|
/_app/api/v1/auth/token-exchange | POST | JWT’en ER autentificeringen | 1t | subject_token (+ valgfrit actor_token), tenant_slug, scopes, name |
/_app/api/v1/oauth/token | POST | Basic ELLER formular-body | 1t | grant_type=client_credentials, client_id, client_secret, scope, audience |
/api/v1/* (alle) | alle | Authorization: Bearer <jwt or ffp_*> | efter exp / efter PAT-rækken | — |
Relateret
- SSO-opsætning (OIDC): registrér din IdP til både menneskeligt login og agent-autentificering
- Workflow-OIDC til cloud-autentificering: den udgående retning (runner-VM → tillidspolitikker i AWS/GCP/Azure)
- Revisionslog og kæde: verificér, hvad hver agent har gjort, ved at filtrere på personen eller på agenten
- API-tokens (PAT’er): langlivede tokens, som mennesker opretter, til den samme
/api/v1/*-flade; opret dem under Brugerindstillinger → API-tokens, og se Godkendelsespolitik for maksimal levetid og rotationspolitik