Flow
Sign-in and authorization

Sign-in and authorization

How Kratos, Hydra, the identity service, jwtauth and authz fit together, and what exists today.

Every request to a Flow service answers two questions. Who is calling? is answered by a signed access token. May they do this? is answered by Cedar policies that the service evaluates itself. This page explains which piece does what. The guides after it show how to build on them:

The binding rules are in docs/architecture.md and the decisions docs/decisions/0013-jwt-validated-in-each-service.md and docs/decisions/0047-ory-for-authentication-identity-stays-first-party.md.

The pieces

PieceWhereWhat it does
Kratosservices/authnHolds people's accounts: passwords, Google sign-in, the browser session. It proves who a person is.
Hydraservices/authnThe OAuth2 and OpenID Connect server. It issues short-lived JWT access tokens and publishes the keys that sign them.
Sign-in app (authn-app)frontend/authn-appThe screens people see: sign in, register, and the login, consent and logout steps Hydra hands off to it.
identity serviceservices/identityHolds every Principal: A person or a service that the identity service knows about, identified by its FID, with a kind (person or system) and a status (active or disabled). Having a principal grants nothing on its own; access comes from grants. and its Grant: A role held by a principal at a scope: global, one building, or one unit. Grants live in the identity service and are the only input to authorization besides the endpoint_access data that says which roles an action allows.s: which role it has, at which scope. Stores no personal data.
profile serviceservices/profileGives an app the signed-in person's own email and name, read from their Kratos identity when the app asks. Stores nothing.
User admin app (users-app)frontend/users-appWhere administrators find people, manage roles and grants, and create and manage service accounts, under Clients. Served at /admin on the sign-in host. Signs a person in through Hydra as the public client flow-users-app, and calls identity with that person's own access token.
common/jwtauthcommon/jwtauthGo middleware. Verifies a Hydra access token and puts the caller's FID: The id of a principal. For a person it is their Kratos identity id, a lowercase UUID; for a service it is its OAuth2 client_id. It arrives as the verified sub claim of a Hydra access token and becomes the actor on the request. on the request context.
common/authzcommon/authzGo package. Evaluates the service's Cedar policies against the caller's grants.

Two things are deliberately kept apart. Authentication, proving who someone is, belongs to Ory (Kratos and Hydra). Authorization data, the roles people hold, belongs to the identity service. A token never carries roles. A service reads roles from identity's data, so a revoked grant takes effect without waiting for a token to expire.

A token carries no personal data either: it names the person only by their FID: The id of a principal. For a person it is their Kratos identity id, a lowercase UUID; for a service it is its OAuth2 client_id. It arrives as the verified sub claim of a Hydra access token and becomes the actor on the request.. Their email and name stay in Kratos, and an app that needs them asks the profile service, GET /v1/me/profile, with the person's token. The profile service reads them from Kratos on each request and keeps no copy.

How a request is authorized

A person signs in once, through the sign-in app. Their web app then holds an access token and sends it with every call:

Diagram. Text representation: sequenceDiagram participant B as Browser (web app) participant H as Hydra participant A as Sign-in app participant K as Kratos participant S as Service B->>H: /oauth2/auth (client_id, PKCE challenge, audience) H->>A: /login?login_challenge=... A->>K: sign in (password or Google) K->>H: accept login, subject = Kratos identity id H->>A: /consent?consent_challenge=... A->>H: accept (first-party clients only) H->>B: redirect with code B->>H: /oauth2/token (code + PKCE verifier) H->>B: access token (JWT) and ID token B->>S: request with Authorization: Bearer <access token> S->>S: jwtauth verifies the token, sub becomes the actor S->>S: authz evaluates Cedar with the actor's grants
Diagram source
sequenceDiagram
  participant B as Browser (web app)
  participant H as Hydra
  participant A as Sign-in app
  participant K as Kratos
  participant S as Service
  B->>H: /oauth2/auth (client_id, PKCE challenge, audience)
  H->>A: /login?login_challenge=...
  A->>K: sign in (password or Google)
  K->>H: accept login, subject = Kratos identity id
  H->>A: /consent?consent_challenge=...
  A->>H: accept (first-party clients only)
  H->>B: redirect with code
  B->>H: /oauth2/token (code + PKCE verifier)
  H->>B: access token (JWT) and ID token
  B->>S: request with Authorization: Bearer <access token>
  S->>S: jwtauth verifies the token, sub becomes the actor
  S->>S: authz evaluates Cedar with the actor's grants

Inside the service there are always two steps, in this order:

  1. Verify the token with common/jwtauth. A missing, expired or forged token is a 401, and no handler runs.
  2. Check the permission with common/authz. A refusal is a 403.

A service never trusts a header that names the caller, and there is no development bypass. It trusts only a token whose signature it checked itself.

People and services are both principals

KindFIDHow it signs in
personThe Kratos identity id, a lowercase UUIDThrough a web or mobile app, with the authorization code flow
systemThe OAuth2 client_id: flow-sample-service for a client in the repository, svc- and a name for a service account made in users-appWith its own client secret, using the client_credentials grant

A person's principal is created automatically when they register: Kratos calls a webhook on the identity service. Having a principal grants nothing. Access comes from grants such as staff@building:<uuid> or platform-admin@global:*, added in the user admin app.

What exists today and what does not

The auth system is deployed to dev only. No existing service uses it yet, and each service adopts it as its own change.

Built and on devStill to come
Kratos, Hydra and the sign-in app, including OAuth2 login, consent and logoutProduction deployment
The identity service: principals and grants, disabling a principal, the role registry with roles for people and roles for service accounts, the admin audit record, and GET /v1/authz/me, which tells an app what the caller may doHydra's admin port restricted to identity and the client bootstrap, which production needs first
The profile service, GET /v1/me/profileRenaming and deleting roles
The user admin app: people, grants, roles and the actions each role may do, and Clients, where service accounts are created, given roles, rotated, disabled and deletedExisting services adopting common/jwtauth and common/authz
identity checking its own admin requests with Cedar and endpoint access, instead of trusting the user admin app's key alone
Service accounts: identity creates them in Hydra, each with a system principal, and shows the secret once
common/jwtauth, common/authz, and common/principalcopy, the local copy of identity's principals and grants that is the database Store for common/authzThe legacy deepsky backend accepting Hydra tokens
OAuth2 clients defined in the repository and applied on every deploy. The deploy changes only clients tagged managed_by: repo and prints a report of every other differenceCookie names that say Flow rather than Ory

The sample service, services/sample, is built and runs locally only. It uses all three packages, and the services guide follows its code. It is a reference to copy and is never deployed.

The sample web app, frontend/sample-app, is also built and runs locally only. It signs a person in through Hydra, shows who is signed in from the profile service, calls the sample service with the person's access token, and signs them out again, and the web apps guide follows its code.

The profile service, services/profile, is built and on dev.

The two sample clients, flow-sample-web and flow-sample-service, are registered in Hydra on dev and locally. The sample web app signs people in with the first. The second is a machine client that calls the sample service with its own token.

A client that signs people in is always defined in the repository, where every change is reviewed. A machine client can be defined there too, or created as a service account in the user admin app, under Clients: see Building a service.

On this page