Agentnativ autentisering
fremforge tar emot tre autentiseringsmönster för OAuth2 / OIDC för icke-mänskliga anropare (agenter, skript, AI-assistenter, CI-runners, integrationer från leverantörer). Alla tre bygger på samma tillåtelselistor för scopes per tenant och samma granskningskedja som användarinloggning: ingen skuggväg och inget separat förtroendelager.
Den gemensamma förutsättningen för varje flöde som involverar kundens IdP: registrera din IdP som en OIDC-autentiseringskälla under Organisationsadmin → SSO → Autentiseringskällor, verifiera domänen och konfigurera tillåtelselistor för scopes per app på redigeringssidan för autentiseringskällan under Exchangeable scopes. Samma registrering driver federerad inloggning för människor, så en kund som använder OIDC-SSO har redan grunden på plats.
Implementeringsnotering: Endpointerna
/_app/api/v1/auth/token-exchangeoch/_app/api/v1/oauth/tokenbetjänas av fremforges kontrollplan; de är inte inbyggda Forgejo-endpoints. SDK-författare ska rikta in sig påhttps://frem.sh/_app/api/v1/*exakt som det är dokumenterat.
De tre flödena i korthet
| Flöde | Specifikation | Kundens IdP:s roll | Passar bäst för |
|---|---|---|---|
| 1. Direkt JWT-bearer | — (informellt, “Bearer JWT”) | Utfärdar JWT:n; fremforge verifierar | Arbetsbelastningsidentiteter (Entra workload identity, Okta-tjänstkonto, Google-tjänstkonto) som redan skapar JWT:er bundna till en tenant |
| 2. Tokenutbyte (RFC 8693) | RFC 8693 | Utfärdar subject_token (+ valfri actor_token för OBO) | CI-arbetsflöden, schemalagda agenter, on-behalf-of-flöden (OBO) där granskningen måste namnge personen och agenten som agerar för hen |
3. client_credentials (RFC 6749 §4.4) | RFC 6749 | Ingen; fremforge utfärdar både client_id och client_secret | Appar som inte har någon IdP alls, eller där kunden föredrar fremforge som IdP |
Alla tre flödena ger en kortlivad (≤ 1 timme) ffp_-åtkomsttoken som är bunden till en tenant och en uppsättning scopes. RBAC, hastighetsbegränsning, granskningshändelser och spärrarna för tenantisolering är identiska med en PAT som en människa har skapat.
Flöde 1 — JWT från kundens IdP direkt som bearer
Agenten visar upp sin JWT från IdP:n direkt som Authorization: Bearer <jwt> på valfri /api/v1/*-endpoint. fremforge verifierar JWT:n mot din registrerade autentiseringskälla, tillämpar snittet av scopes från källans app-grant och returnerar svaret: ingen extra rundresa och ingen lagrad inloggningsuppgift.
Endpoint: valfri route under frem.sh/_app/api/v1/*.
Konfiguration (en gång, per autentiseringskälla):
- Organisationsadmin → SSO → registrera en OIDC-källa för din IdP (eller återanvänd en befintlig som används för inloggning av människor).
- Konfigurera tillåtelselistor för scopes per app under Edit auth source → App grants. Tillåtelselistan binder en IdP-applikation (matchad på JWT:ns claim
azpelleraud[0]) till en största uppsättning scopes. - Organisationsadmin → Programmatisk åtkomst → Hantera JWT-bearer och utbyte → slå på Tillåt direkt JWT-bearer för källan.
- (Bara IdP:er med flera tenants) Ange en claim-assertion som binder källan till din tenant:
{"tid": "<your-Entra-tenant-guid>"}för Entra/common/,{"hd": "yourdomain.com"}för Google Workspace osv. Adminformuläret vägrar slå på direkt bearer för en utfärdar-URL med flera tenants utan claim-assertions.
Krav på token:
issmatchar den registrerade källans URL (normaliserad, skiftlägesokänslig, tål ett avslutande snedstreck).audmatchar antingen källans konfigurerade överstyrning av audience ELLER den kanoniska audience per tenant,https://frem.sh/<your-tenant-slug>.expligger i framtiden ochiatinte orimligt långt bakåt (jose tillämpar 30 sekunders klockavvikelse).- Alla
claim_assertionssom är konfigurerade för källan matchar den verifierade nyttolasten. - Signeringsnyckeln hämtas via OIDC discovery + JWKS (cachad i 10 minuter, och kunden styr den genom IdP:ns vanliga nyckelrotation).
Exempel (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/reposSäkerhetsegenskaper:
- JWT:n är inloggningsuppgiften. Ingen PAT-rad skapas i fremforges databas. Återkallelse = låt
explöpa ut, ELLER ta bort IdP-källan / stäng av direkt bearer (nästa anrop avvisas). - Bindning mellan tenants är omöjlig genom konstruktionen: en audience per tenant binder JWT:n till en slug, och tabellen auth_sources har en UNIQUE-begränsning på databasnivå som hindrar två tenants från att registrera samma utfärdare för direkt bearer.
- Taket för scopes kommer från källans app-grant. Scopes som flyttar pengar (
billing:write,sso:write,policy:write,orgs:write) kräver uttryckliga poster i tillåtelselistan per app; de beviljas ALDRIG av en tillåtelselista som är NULL.
Flöde 2 — tokenutbyte enligt RFC 8693 (med OBO)
Agenten visar upp en JWT från IdP:n som subject_token och byter den mot en kortlivad fremforge-PAT (ffp_). Parametern actor_token möjliggör delegering on-behalf-of: agenten agerar för en namngiven person. fremforge har inga agentidentiteter: personen är aktören i granskningen, och agenten är en detalj på varje granskningsrad.
Endpoint: POST frem.sh/_app/api/v1/auth/token-exchange.
Exempel (enkelt utbyte):
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"
}'Exempel (OBO — agenten agerar på uppdrag 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"]
}'Begär bara scopes som tokenutbytet kan bevilja: den utbytbara uppsättningen (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), begränsad av app-granten för din autentiseringskälla. En scope utanför någon av dem tas bort, och ett anrop där inget återstår avvisas med no-scopes-grantable. Åtkomst till repon och issues ingår inte i fremforges uppsättning API-scopes och kan därför inte bytas till.
Varje granskningshändelse som den skapade PAT:en skriver namnger Alice som actor, som oidc:idp:<alice-sub> (den verifierade sub i subject token). Agenten är en detalj på raden: fields.agent är oidc:agent:<agent-sub> (den verifierade sub i actor token), och fields.via_token är id:t för den skapade PAT:en. Själva utbytet registreras som api.token.exchange.obo, också med Alice som aktör och agenten i fields.agent. Båda värdena kommer enbart från de verifierade JWT:erna; inget i anropets body kan namnge eller ändra personen.
Utan actor_token namnger ett utbyte från kundens IdP fortfarande personen som aktör, med via_token och utan agent. Ett utbyte med runner-OIDC har ingen person (ett CI-jobb är ingen person), så dess händelser behåller actor=token:<id>.
Compliancefrågor som “varje åtgärd min Anthropic-agent utförde på uppdrag av Alice förra kvartalet” filtrerar granskningsloggen på Alice och läser fields.agent. Aktörsfiltret matchar aktören, kolumnen on_behalf_of och fields.agent, så ett filter på oidc:agent:<agent-sub> hittar allt den agenten gjorde, för vilken person som helst. I adminvyns granskningslogg matchar filtret en delsträng och sådana rader har en etikett via agent; GET /orgs/{org}/audit?actor= matchar exakt. Händelser som skrevs före 2026-10-23 namngav agenten som aktör och Alice i on_behalf_of. Hashkedjan täcker dem, så de behåller den formen, visas som på uppdrag av Alice, och samma filter hittar dem.
Skillnad mot flöde 1: tokenutbyte skapar en lagrad PAT (så att den kan bära en uttrycklig delmängd scopes och ett eget namn). Flöde 1 är flyktigt. Välj utbyte när agenten gör många anrop under en och samma körning av ett arbetsflöde; välj direkt bearer när varje anrop är fristående.
Flöde 3 — client_credentials (fremforge som IdP)
För appar utan IdP, eller där kunden föredrar en enda uppsättning inloggningsuppgifter som hanteras inne i fremforge. Tenant-administratören skapar ett par (client_id, client_secret), appen autentiserar sig med Authorization: Basic (eller ett POST-formulär) och får en kortlivad ffp_-PAT.
Konfiguration:
- Organisationsadmin → Programmatisk åtkomst → Hantera JWT-bearer och utbyte → Skapa ny klient (under avsnittet
client_credentials). - Välj ett namn, ett tak för scopes och en valfri tillåtelselista för audience.
- Kopiera
client_secretdirekt: den visas en gång och aldrig igen.
Endpoint: POST frem.sh/_app/api/v1/oauth/token.
Exempel:
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: tryck när som helst på knappen Rotera i adminvyn. Den nuvarande hemligheten är giltig sida vid sida med den nya i 24 timmar, så att du kan byta din konfiguration utan avbrott. Efter 24-timmarsfönstret autentiserar bara den nya hemligheten.
Återkallelse: ett klick på Återkalla ogiltigförklarar klienten OCH återkallar i kaskad varje ffp_-token den någonsin skapat, i en enda transaktion. Det är felsäkert även om operatörens pod dödas mitt under återkallelsen.
Det som de tre flödena har gemensamt
| Egenskap | Hur den upprätthålls |
|---|---|
| Tenantisolering | Varje skapad token har tenant_id satt; requireTenantBinding avvisar anrop mellan tenants med 404. UNIQUE på databasnivå hindrar att en utfärdare görs anspråk på av två tenants för direkt bearer. |
| Tak för scopes | Alla tre tar snittet mot en utbytbar uppsättning scopes som operatören styr OCH kundens tillåtelselista per IdP / per klient. Scopes som flyttar pengar (billing:write, sso:write, policy:write, orgs:write) kräver uttryckliga poster i tillåtelselistan; de beviljas aldrig som standard. |
| TTL | Alla skapade tokens har ett tak på 1 timme. Kunder som vill ha inloggningsuppgifter med längre livslängd använder vägen via adminvyn (personliga åtkomsttokens). |
| Granskningskedja | Varje skapande ger en audit_event med actor, åtgärd och scopes. För ett utbyte från kundens IdP är aktören personen, och agenten (för OBO) är fields.agent; varje senare händelse som tokenen skriver har personen som aktör plus agent och via_token. Kedjans hash täcker fields_json, inklusive båda. |
| Hastighetsbegränsning | Token-endpointerna är hastighetsbegränsade per IP. /auth/token-exchange och /oauth/token delar ett tak på 30/min för att begränsa CPU-kostnaden för JWT-verifiering av falska indata. |
| Nåbarhet för JWKS | Alla utgående hämtningar av JWKS och discovery går genom en begränsad utgående gateway: SSRF, privata IP-adresser och molnmetadata blockeras vid nätverkshoppet. JWKS cachas i 10 min. |
| Dataresidens i EU | Flödet når aldrig en Sigstore- / Auth0- / Okta-endpoint i USA; verifieringen sker helt inom fremforges närvaro i T Cloud Public eu-de. Utgående trafik till kundens IdP går bara genom den SSRF-skyddade hämtningen via proxy. |
Sammanfattning av hotmodellen
| Hot | Motåtgärd |
|---|---|
Kunden registrerar en Entra /common/-URL med flera tenants utan claim_assertion för tid | Adminformuläret vägrar; hjälpfunktionen isMultiTenantIssuerUrl() flaggar Entra (v1 + v2), Google Workspace och Microsofts Gov- / Kina-moln |
Två tenants delar en iss-URL för en IdP | Partiellt UNIQUE-index på (LOWER(RTRIM(iss,'/'))) WHERE direkt bearer är påslagen; den andra registreringen avvisas i databaslagret |
| JWT från kundens IdP spelas upp mot fel tenant | Audience per tenant https://frem.sh/<slug>: en token skapad för aud=https://frem.sh/acme kan bokstavligen inte klara tenanten initechs kontroll av audience |
Läckt client_secret | Tenant-administratören kör Återkalla → kaskadtransaktionen dödar klienten OCH varje PAT den skapat; rotation stöder ett fönster på 24 h med två hemligheter för byte utan avbrott |
| Förfalskade claims i OBO-actor_token | jose verifierar signatur + iss + aud mot samma källa som subject; sub är oföränderlig, och email/name litas aldrig på |
| Inaktuell JWKS-cache efter nyckelrotation i IdP:n | Hård TTL på 10 min, driftmässigt begränsad |
| Nätverksinternt värdnamn i IdP:ns discovery-dokument (DNS rebind / SSRF) | Den begränsade utgående gatewayen blockerar hämtningen; ett https://-skydd på kodnivå lägger till försvar på djupet |
Snabbreferens
| Sökväg | Metod | Autentisering | TTL | Body |
|---|---|---|---|---|
/_app/api/v1/auth/token-exchange | POST | JWT:n ÄR autentiseringen | 1h | subject_token (+ valfri actor_token), tenant_slug, scopes, name |
/_app/api/v1/oauth/token | POST | Basic ELLER formulär-body | 1h | grant_type=client_credentials, client_id, client_secret, scope, audience |
/api/v1/* (valfri) | valfri | Authorization: Bearer <jwt or ffp_*> | enligt exp / enligt PAT-raden | — |
Relaterat
- SSO-konfiguration (OIDC) — registrera din IdP för både inloggning av människor och agentautentisering
- Arbetsflödes-OIDC för molnautentisering — den utgående riktningen (runner-VM → förtroendepolicyer i AWS/GCP/Azure)
- Granskningslogg och kedja — verifiera vad varje agent gjorde, med filter på personen eller på agenten
- API-tokens (PAT:er) — tokens med lång livslängd som människor skapar, för samma
/api/v1/*-yta; skapa dem under Användarinställningar → API-token, och se Autentiseringspolicy för policy om maximal livslängd och rotation