Naar de hoofdinhoud

Authenticatie voor agents

fremforge accepteert drie authenticatiepatronen met OAuth2 / OIDC voor niet-menselijke aanroepers (agents, scripts, AI-assistenten, CI-runners, integraties van leveranciers). Alle drie gebruiken dezelfde toegestane scopes per tenant en hetzelfde mechanisme van de auditketen als het inloggen van gebruikers: geen schaduwpad, geen aparte trust store.

De gedeelde voorwaarde voor elke flow waarbij de IdP van de klant betrokken is: registreer je IdP als OIDC-authenticatiebron onder Organisatiebeheer → SSO → Auth-bronnen, verifieer het domein en stel de toegestane scopes per app in op de bewerkpagina van de authenticatiebron, onder Exchangeable scopes. Dezelfde registratie zorgt voor de federatie van het inloggen door mensen, dus een klant die OIDC-SSO gebruikt, heeft de basis al staan.

Opmerking over de implementatie: De endpoints /_app/api/v1/auth/token-exchange en /_app/api/v1/oauth/token worden geleverd door het control plane van fremforge; het zijn geen eigen endpoints van Forgejo. Auteurs van SDK’s moeten https://frem.sh/_app/api/v1/* precies zo aanspreken als hier beschreven.

De drie flows in één oogopslag

FlowSpecificatieRol van de IdP van de klantHet meest geschikt voor
1. Directe JWT-bearer— (informeel, “Bearer JWT”)Geeft de JWT uit; fremforge verifieertWorkload-identiteiten (Entra workload identity, Okta-serviceaccount, Google-serviceaccount) die al JWT’s uitgeven die aan een tenant gebonden zijn
2. Tokenuitwisseling (RFC 8693)RFC 8693Geeft het subject_token uit (+ optioneel een actor_token voor OBO)CI-workflows, geplande agents, on-behalf-of-flows (OBO) waarin de audit de persoon moet noemen en de agent die namens die persoon handelt
3. client_credentials (RFC 6749 §4.4)RFC 6749Geen: fremforge geeft zowel client_id als client_secret uitApps die helemaal geen IdP hebben, of waarbij de klant fremforge als IdP verkiest

Alle drie de flows leveren een kortlevend (≤ 1 uur) toegangstoken ffp_ op dat gebonden is aan één tenant en één set scopes. RBAC, rate limiting, auditgebeurtenissen en de controles op scheiding van tenants zijn identiek aan die van een PAT die door een mens is aangemaakt.

Flow 1: JWT van de IdP van de klant direct als bearer

De agent biedt zijn door de IdP uitgegeven JWT direct aan als Authorization: Bearer <jwt> op elk endpoint /api/v1/*. fremforge verifieert de JWT tegen je geregistreerde authenticatiebron, past de doorsnede van scopes uit de app-toekenning van de bron toe en geeft het antwoord terug: geen extra round-trip, geen opgeslagen inloggegeven.

Endpoint: elke route frem.sh/_app/api/v1/*.

Instellen (eenmalig, per authenticatiebron):

  1. Organisatiebeheer → SSO → registreer een OIDC-bron voor je IdP (of hergebruik een bestaande die al voor het inloggen van mensen wordt gebruikt).
  2. Stel de toegestane scopes per app in onder Edit auth source → App grants. De lijst koppelt een IdP-applicatie (herkend aan de claim azp of aud[0] van de JWT) aan een maximale set scopes.
  3. Organisatiebeheer → Programmatische toegang → JWT-bearer en uitwisseling beheren → schakel Directe JWT-bearer toestaan in voor de bron.
  4. (Alleen IdP’s voor meerdere tenants) Vul een claim assertion in die de bron aan je tenant bindt: {"tid": "<your-Entra-tenant-guid>"} voor Entra /common/, {"hd": "yourdomain.com"} voor Google Workspace, enzovoort. Het beheerformulier weigert directe bearer in te schakelen op een issuer-URL voor meerdere tenants zonder claim assertions.

Eisen aan het token:

  • iss komt overeen met de URL van de geregistreerde bron (genormaliseerd, zonder onderscheid tussen hoofd- en kleine letters, een slash aan het eind maakt niet uit).
  • aud komt overeen met de ingestelde Audience override van de bron OF met de canonieke audience per tenant https://frem.sh/<your-tenant-slug>.
  • exp ligt in de toekomst, iat ligt niet onredelijk ver in het verleden (jose hanteert een klokafwijking van 30 seconden).
  • Alle claim_assertions die op de bron zijn ingesteld, komen overeen met de geverifieerde payload.
  • De ondertekeningssleutel wordt gevonden via OIDC-discovery + JWKS (10 minuten gecachet, door de klant te sturen via de gewone sleutelrotatie van de IdP).

Voorbeeld (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

Beveiligingseigenschappen:

  • De JWT is het inloggegeven. Er wordt geen PAT-rij in de database van fremforge aangemaakt. Intrekken = exp laten verlopen, OF de IdP-bron verwijderen / directe bearer uitschakelen (het volgende verzoek wordt geweigerd).
  • Binding over tenants heen is per constructie onmogelijk: een audience per tenant bindt de JWT aan één slug, en de tabel auth_sources dwingt een UNIQUE-constraint op databaseniveau af die voorkomt dat twee tenants dezelfde issuer voor directe bearer registreren.
  • Het maximum aan scopes komt uit de app-toekenning van de bron. Scopes die met geld of controle te maken hebben (billing:write, sso:write, policy:write, orgs:write) vereisen expliciete items per app op de lijst; ze worden NOOIT toegekend door een lijst die NULL is.

Flow 2: tokenuitwisseling volgens RFC 8693 (met OBO)

De agent biedt een door de IdP uitgegeven JWT aan als subject_token en wisselt die in voor een kortlevende PAT van fremforge (ffp_). De parameter actor_token maakt delegatie namens iemand mogelijk: de agent handelt voor een bij naam genoemde persoon. fremforge kent geen identiteiten voor agents: de persoon is de actor in de audit, en de agent is een detail van elke auditrij.

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

Voorbeeld (gewone uitwisseling):

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"
  }'

Voorbeeld (OBO: de agent handelt namens 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"]
  }'

Vraag alleen scopes aan die de tokenuitwisseling kan toekennen: de uitwisselbare set (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), beperkt door de app-toekenning op je authenticatiebron. Een scope buiten een van beide wordt weggelaten, en een verzoek waarin niets overblijft, wordt geweigerd met no-scopes-grantable. Toegang tot repository’s en issues zit niet in de scopeset van de fremforge-API en kan dus niet worden uitgewisseld.

Elke auditgebeurtenis die de aangemaakte PAT schrijft, noemt Alice als actor, als oidc:idp:<alice-sub> (de geverifieerde sub van het subject token). De agent is een detail van de rij: fields.agent is oidc:agent:<agent-sub> (de geverifieerde sub van het actor token), en fields.via_token is de id van de aangemaakte PAT. De uitwisseling zelf wordt vastgelegd als api.token.exchange.obo, ook met Alice als actor en de agent in fields.agent. Beide waarden komen alleen uit de geverifieerde JWT’s; niets in de body van het verzoek kan de persoon noemen of wijzigen.

Zonder actor_token noemt een uitwisseling met de IdP van de klant nog steeds de persoon als actor, met via_token en zonder agent. Een uitwisseling met runner-OIDC heeft geen persoon (een CI-job is geen persoon), dus de gebeurtenissen daarvan houden actor=token:<id>.

Compliancevragen zoals “elke actie die mijn Anthropic-agent het afgelopen kwartaal namens Alice heeft uitgevoerd” filteren het auditlog op Alice en lezen fields.agent. Het actorfilter matcht de actor, de kolom on_behalf_of en fields.agent, dus filteren op oidc:agent:<agent-sub> vindt alles wat die agent deed, voor welke persoon dan ook. In het auditlog in het beheer matcht het filter een deel van de tekst, en zulke rijen hebben een chip via agent; GET /orgs/{org}/audit?actor= matcht exact. Gebeurtenissen van vóór 2026-10-23 noemden de agent als actor en Alice in on_behalf_of. De hashketen dekt ze, dus ze behouden die vorm, worden getoond als namens Alice, en dezelfde filters vinden ze.

Verschil met flow 1: tokenuitwisseling maakt een opgeslagen PAT aan (zodat die een expliciete subset van scopes en een eigen naam kan hebben). Flow 1 is vluchtig. Kies uitwisseling als de agent veel aanroepen doet binnen één workflowrun; kies directe bearer als elke aanroep op zichzelf staat.

Flow 3: client_credentials (fremforge als IdP)

Voor apps zonder IdP, of als de klant liever één set inloggegevens heeft die binnen fremforge wordt beheerd. De beheerder van de tenant maakt een paar (client_id, client_secret) aan, de app authenticeert met Authorization: Basic (of een POST-formulier) en krijgt een kortlevende PAT ffp_.

Instellen:

  1. Organisatiebeheer → Programmatische toegang → JWT-bearer en uitwisseling beheren → Nieuwe client aanmaken (in de sectie client_credentials).
  2. Kies een naam, een maximum aan scopes en een optionele lijst met toegestane audiences.
  3. Kopieer het client_secret meteen: het wordt één keer getoond en daarna nooit meer.

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

Voorbeeld:

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"

Antwoord:

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

Rotatie: klik op elk moment op de knop Roteren in de beheeromgeving. Het huidige secret blijft 24 uur geldig naast het nieuwe, zodat je je configuratie zonder downtime kunt omzetten. Na die 24 uur authenticeert alleen het nieuwe secret.

Intrekken: een klik op Intrekken maakt de client ongeldig EN trekt in één transactie elk ffp_-token in dat de client ooit heeft aangemaakt; dat is fail-safe, ook als de operator-pod halverwege het intrekken wordt beëindigd.

Wat de drie flows gemeen hebben

EigenschapHoe die wordt afgedwongen
Scheiding van tenantsElk aangemaakt token heeft tenant_id ingesteld; requireTenantBinding weigert aanroepen naar een andere tenant met 404. Een UNIQUE op databaseniveau voorkomt dat één issuer door twee tenants voor directe bearer wordt geclaimd.
Maximum aan scopesAlle drie nemen de doorsnede met een uitwisselbare scopeset die de operator beheert EN de lijst per IdP / per client van de klant. Scopes die met geld of controle te maken hebben (billing:write, sso:write, policy:write, orgs:write) vereisen expliciete items op de lijst en worden nooit standaard toegekend.
TTLAlle aangemaakte tokens zijn hooguit 1 uur geldig. Klanten die langer geldige inloggegevens willen, gebruiken de aanmaakroute in de beheeromgeving (Personal Access Tokens).
AuditketenElke aanmaak schrijft een audit_event met actor, actie en scopes. Bij een uitwisseling met de IdP van de klant is de actor de persoon en is de agent (bij OBO) fields.agent; elke latere gebeurtenis die het token schrijft, heeft de persoon als actor plus agent en via_token. De hash van de keten dekt fields_json, inclusief beide.
Rate limitingDe tokenendpoints hebben een limiet per IP-adres. /auth/token-exchange en /oauth/token delen een limiet van 30 per minuut, om de CPU-kosten van het verifiëren van JWT’s bij nepinvoer te begrenzen.
Bereikbaarheid van JWKSAlle uitgaande verzoeken voor JWKS en discovery gaan via een beperkte egress-gateway: SSRF, privé-IP-adressen en cloudmetadata worden op netwerkniveau geblokkeerd. JWKS wordt 10 minuten gecachet.
Gegevens in de EUDe pijplijn bereikt nooit een in de VS gehost endpoint van Sigstore / Auth0 / Okta: de verificatie gebeurt volledig binnen de omgeving van fremforge in T Cloud Public eu-de. Uitgaand verkeer naar de IdP van de klant gaat alleen via het opvragen met SSRF-proxy.

Samenvatting van het dreigingsmodel

DreigingMaatregel
Klant registreert een Entra-URL /common/ voor meerdere tenants zonder claim_assertion tidHet beheerformulier weigert; de helper isMultiTenantIssuerUrl() markeert Entra (v1 + v2), Google Workspace en de Microsoft-clouds voor de overheid / China
Twee tenants delen een iss-URL van een IdPGedeeltelijke UNIQUE-index op (LOWER(RTRIM(iss,'/'))) WHERE directe bearer is ingeschakeld: de tweede registratie wordt in de databaselaag geweigerd
JWT van de IdP van de klant opnieuw gebruikt tegen de verkeerde tenantAudience per tenant https://frem.sh/<slug>: een token dat is aangemaakt voor aud=https://frem.sh/acme kan letterlijk niet slagen voor de audiencecontrole van tenant initech
Gelekt client_secretDe beheerder van de tenant klikt op Intrekken → de transactie trekt de client EN elke PAT die hij aanmaakte in; rotatie ondersteunt een venster van 24 uur met twee secrets voor een wissel zonder downtime
Vervalste claims in het actor_token bij OBOjose verifieert handtekening + iss + aud tegen dezelfde bron als het subject; sub is onveranderlijk, email/name worden nooit vertrouwd
Verouderde JWKS-cache na een sleutelrotatie bij de IdPHarde TTL van 10 minuten: operationeel begrensd
Interne hostnaam in het discoverydocument van de IdP (DNS-rebinding / SSRF)De beperkte egress-gateway blokkeert het verzoek; een https://-controle in de code voegt een extra verdedigingslaag toe

Snelle referentie

PadMethodeAuthenticatieTTLBody
/_app/api/v1/auth/token-exchangePOSTDe JWT IS de authenticatie1 usubject_token (+ optioneel actor_token), tenant_slug, scopes, name
/_app/api/v1/oauth/tokenPOSTBasic OF formulierbody1 ugrant_type=client_credentials, client_id, client_secret, scope, audience
/api/v1/* (elk)elkAuthorization: Bearer <jwt or ffp_*>volgens exp / volgens de PAT-rij—

Gerelateerd

  • SSO instellen (OIDC): registreer je IdP voor zowel het inloggen door mensen als de authenticatie van agents
  • Workflow-OIDC voor cloudauthenticatie: de uitgaande richting (runner-VM → trust policies van AWS/GCP/Azure)
  • Auditlog en keten: controleer wat elke agent heeft gedaan, door te filteren op de persoon of op de agent
  • API-tokens (PAT’s): langlevende tokens die door mensen worden aangemaakt, voor hetzelfde oppervlak /api/v1/*; maak ze aan onder Gebruikersinstellingen → API-tokens, zie Authenticatiebeleid voor de maximale levensduur en het rotatiebeleid