Hoppa till huvudinnehållet

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-exchange och /_app/api/v1/oauth/token betjä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ödeSpecifikationKundens IdP:s rollPassar bäst för
1. Direkt JWT-bearer— (informellt, “Bearer JWT”)Utfärdar JWT:n; fremforge verifierarArbetsbelastningsidentiteter (Entra workload identity, Okta-tjänstkonto, Google-tjänstkonto) som redan skapar JWT:er bundna till en tenant
2. Tokenutbyte (RFC 8693)RFC 8693Utfä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 6749Ingen; fremforge utfärdar både client_id och client_secretAppar 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):

  1. 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).
  2. 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 azp eller aud[0]) till en största uppsättning scopes.
  3. Organisationsadmin → Programmatisk åtkomst → Hantera JWT-bearer och utbyte → slå på Tillåt direkt JWT-bearer för källan.
  4. (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:

  • iss matchar den registrerade källans URL (normaliserad, skiftlägesokänslig, tål ett avslutande snedstreck).
  • aud matchar antingen källans konfigurerade överstyrning av audience ELLER den kanoniska audience per tenant, https://frem.sh/<your-tenant-slug>.
  • exp ligger i framtiden och iat inte orimligt långt bakåt (jose tillämpar 30 sekunders klockavvikelse).
  • Alla claim_assertions som ä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/repos

Säkerhetsegenskaper:

  • JWT:n är inloggningsuppgiften. Ingen PAT-rad skapas i fremforges databas. Återkallelse = låt exp lö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:

  1. Organisationsadmin → Programmatisk åtkomst → Hantera JWT-bearer och utbyte → Skapa ny klient (under avsnittet client_credentials).
  2. Välj ett namn, ett tak för scopes och en valfri tillåtelselista för audience.
  3. Kopiera client_secret direkt: 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

EgenskapHur den upprätthålls
TenantisoleringVarje 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 scopesAlla 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.
TTLAlla 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).
GranskningskedjaVarje 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änsningToken-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 JWKSAlla 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 EUFlö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

HotMotåtgärd
Kunden registrerar en Entra /common/-URL med flera tenants utan claim_assertion för tidAdminformulä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 IdPPartiellt 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 tenantAudience 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_secretTenant-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_tokenjose 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:nHå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ägMetodAutentiseringTTLBody
/_app/api/v1/auth/token-exchangePOSTJWT:n ÄR autentiseringen1hsubject_token (+ valfri actor_token), tenant_slug, scopes, name
/_app/api/v1/oauth/tokenPOSTBasic ELLER formulär-body1hgrant_type=client_credentials, client_id, client_secret, scope, audience
/api/v1/* (valfri)valfriAuthorization: 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