BE-760: Verify Kratos sessions in the Graph API - #9219
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryCursor Bugbot is generating a summary for commit 5a8fa52. Configure here. |
There was a problem hiding this comment.
Pull request overview
Adds Kratos-backed session authentication to the Graph REST API.
Changes:
- Verifies session headers/cookies through Kratos and resolves provisioned user actors.
- Wires authentication configuration into the Graph server and Compose.
- Adds unit and end-to-end authentication coverage.
Reviewed changes
Copilot reviewed 25 out of 27 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
yarn.lock |
Updates generated dependency resolution. |
tests/graph/test-data/rust/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
tests/graph/http/tests/kratos-session.http |
Adds Kratos session integration scenarios. |
tests/graph/http/test.sh |
Runs the new integration suite. |
libs/@local/temporal-client/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@local/harpc/wire-protocol/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@local/harpc/types/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@local/graph/types/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@local/graph/temporal-versioning/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@local/graph/store/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@local/graph/authorization/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@local/graph/authentication/src/lib.rs |
Exports actor and Kratos modules. |
libs/@local/graph/authentication/src/kratos.rs |
Implements Kratos session verification. |
libs/@local/graph/authentication/src/actor.rs |
Adds principal-store actor resolution. |
libs/@local/graph/authentication/package.json |
Adds generated workspace dependencies. |
libs/@local/graph/authentication/docs/dependency-diagram.mmd |
Refreshes authentication dependencies. |
libs/@local/graph/authentication/Cargo.toml |
Adds provider implementation dependencies. |
libs/@local/graph/api/src/rest/mod.rs |
Installs the Kratos provider. |
libs/@local/graph/api/src/rest/auth.rs |
Re-exports provider integration types. |
libs/@local/graph/api/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@local/codegen/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@local/codec/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
libs/@blockprotocol/type-system/rust/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
infra/compose/compose.yml |
Configures the Graph’s Kratos URL. |
Cargo.lock |
Records new Rust dependencies. |
apps/hash-graph/src/subcommand/server.rs |
Adds session-authentication server configuration. |
apps/hash-graph/docs/dependency-diagram.mmd |
Refreshes generated dependency graph. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## t/be-332-move-authentication-to-the-graph #9219 +/- ##
=============================================================================
+ Coverage 59.73% 59.83% +0.10%
=============================================================================
Files 1423 1425 +2
Lines 139067 139512 +445
Branches 6561 6570 +9
=============================================================================
+ Hits 83067 83483 +416
- Misses 54934 54959 +25
- Partials 1066 1070 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Benchmark results
|
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2002 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 1002 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 3314 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 1527 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 2078 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 1033 | Flame Graph |
policy_resolution_medium
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 102 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 269 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 108 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 133 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 63 | Flame Graph |
policy_resolution_none
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 8 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 3 | Flame Graph |
policy_resolution_small
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 26 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 94 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 27 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 66 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 29 | Flame Graph |
read_scaling_complete
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id;one_depth | 1 entities | Flame Graph | |
| entity_by_id;one_depth | 10 entities | Flame Graph | |
| entity_by_id;one_depth | 25 entities | Flame Graph | |
| entity_by_id;one_depth | 5 entities | Flame Graph | |
| entity_by_id;one_depth | 50 entities | Flame Graph | |
| entity_by_id;two_depth | 1 entities | Flame Graph | |
| entity_by_id;two_depth | 10 entities | Flame Graph | |
| entity_by_id;two_depth | 25 entities | Flame Graph | |
| entity_by_id;two_depth | 5 entities | Flame Graph | |
| entity_by_id;two_depth | 50 entities | Flame Graph | |
| entity_by_id;zero_depth | 1 entities | Flame Graph | |
| entity_by_id;zero_depth | 10 entities | Flame Graph | |
| entity_by_id;zero_depth | 25 entities | Flame Graph | |
| entity_by_id;zero_depth | 5 entities | Flame Graph | |
| entity_by_id;zero_depth | 50 entities | Flame Graph |
read_scaling_linkless
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 1 entities | Flame Graph | |
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 100 entities | Flame Graph | |
| entity_by_id | 1000 entities | Flame Graph | |
| entity_by_id | 10000 entities | Flame Graph |
representative_read_entity
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1
|
Flame Graph |
representative_read_entity_type
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| get_entity_type_by_id | Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba
|
Flame Graph |
representative_read_multiple_entities
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_property | traversal_paths=0 | 0 | |
| entity_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=0 | 0 | |
| link_by_source_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true |
scenarios
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| full_test | query-limited | Flame Graph | |
| full_test | query-unlimited | Flame Graph | |
| linked_queries | query-limited | Flame Graph | |
| linked_queries | query-unlimited | Flame Graph |
🌟 What is the purpose of this PR?
Sessions issued by Ory Kratos now authenticate requests at the Graph REST API. The Kratos-backed provider is the first real credential path through the authentication pipeline introduced in #9218: it verifies the session against the whoami endpoint, resolves the identity's provisioned Graph actor, and only accepts existing user actors.
🔗 Related links
🚫 Blocked by
🔍 What does this change?
hash-graph-authenticationgainskratos::KratosSessionProvider: recognizes theX-Session-Tokenheader and theory_kratos_sessioncookie, verifies the session against the Kratos whoami endpoint, requires an explicitly active session, reads the actor from the identity'smetadata_public.graph_actor_id, and accepts only an existing user actor. Cookies are matched byte-level by name, so unrelated (including non-ASCII) cookies neither trigger verification nor mask the session cookieactor::StorePoolActorResolverresolves actor IDs against the principal store behind theResolveActortrait, keeping the provider independent of the concrete store typesHASH_KRATOS_PUBLIC_URLis required to run the server. The flag is optional at parse time soserver --healthcheckworks without it — the same pattern the admin server uses for its external-service URLsHASH_KRATOS_PUBLIC_URLkratos-session.httpruns the real wire flow against Kratos: provision an identity, log in, authenticate with the session, and verify the rejection pathsPre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
turbo.json's have been updated to reflect thishash-graph-authentication→hash-graph-authorization/hash-graph-storeedges are carried by the generatedpackage.jsondependencies (sync:turborepo); noturbo.jsonchanges are neededHASH_KRATOS_PUBLIC_URL(with network reach to Kratos' public API) before this rolls out — the server refuses to start without it.server --healthcheckparses and probes without the variable.reset-databaseonly wipes the Graph database; the suite uses unique e-mail addresses per run. Cleaning Kratos/Hydra in the reset command is tracked in BE-762 (internal).expires_atis not tracked by the Graph; Kratos' own per-request whoami validation is authoritative.🐾 Next steps
🛡 What tests cover this?
kratos.rswire tests against an in-test fake Kratos that only serves sessions when the expected credential is forwarded: active-flag handling, provisioning, actor-type checks, status-code mapping (including redirects and timeouts), cookie parsing next to non-ASCII cookieskratos-session.httpend-to-end against a real Kratos (runs in CI via the composedevprofile): session token authenticates and is subject to user policies, invalid token is rejected as an invalid session, a rejected session never falls back to the actor-ID header, unprovisioned and unknown-actor identities are rejected with the client-safe messages❓ How to test this?
docker compose --profile dev upininfra/compose) and the Graph (yarn start:graphorcargo run --bin hash-graph --all-features -- server)cd tests/graph/http && yarn reset-database -o none && yarn httpyac send --all tests/kratos-session.httpcd tests/graph/http && sh test.sh🐒 Demo
N/A — backend authentication path; see the httpyac suite output for the wire-level flow.