feat(keycardai-starlette): expose the caller identity claims on the auth context - #269
Merged
Merged
Conversation
…uth context KeycardUser gains sub, sub_profile and keycard_app_id, populated from the verified token. The verifier's AccessToken in keycardai-oauth carries the claims from the JWT to the backend, so it gains the same three optional fields. Co-Authored-By: Larry Osakwe <larry@keycard.ai>
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
KeycardUser now reads sub, sub_profile and keycard_app_id off the verifier's AccessToken, fields that first exist in the oauth release cut from this PR. The workspace resolves locally so tests cannot see the gap; a consumer on any published oauth would AttributeError in authenticate.
Larry-Osakwe
approved these changes
Sep 5, 2026
Larry-Osakwe
deleted the
devin/1788565573-identity-claims-auth-context
branch
September 5, 2026 00:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the auth-context identity fields from
specs/server-bearer-auth/bearer-token-verification-middleware.md(unit row 4) for Python, following the Ruby reference in keycardai/ruby-sdk#24 (ECO-367).KeycardUserinkeycardai-starlettegainssub,sub_profileandkeycard_app_idalongside the existingclient_id, all read from the verified token. The class docstring says which one to key on (keycard_app_idfor the application,client_idfor the credential,subfor the subject,sub_profileto tell a user token from an application token) so a consumer does not infer it fromsubagainstclient_id.Cross-package change. The claims reach the backend through
TokenVerifier.verify_token, whose return type isAccessTokeninkeycardai-oauth(packages/oauth/src/keycardai/oauth/server/verifier.py). That model is the natural carrier, so it gains the same three fields and_verify_tokenpopulates them:subcomes from the RFC 9068subfield ofJWTAccessToken;sub_profileandkeycard_app_idcome fromget_custom_claim. All three are optional (Nonewhen absent) and never fail verification.subis already required byparse_jwt_access_token, so it is present on any conforming token, but the field stays optional per the spec rather than adding a second check.Tests. oauth: a Keycard token populates all four fields; a token without the Keycard claims verifies with
sub_profileandkeycard_app_idNone. starlette: the same two cases throughKeycardAuthBackend.authenticate, plusKeycardUserdefault and application-token (sub == keycard_app_id,sub_profile == "app") cases. Existing tests are untouched except for two shared fixture helpers:create_mock_jwt_access_tokenin the oauth verifier tests now setstoken.sub(theMock(spec=JWTAccessToken)otherwise raises on the new attribute read) and_stub_verifierin the starlette tests accepts the identity fields. New claims are read beforeresourcein_verify_tokenso the existingassert_called_with("resource")assertion holds.MCP surface.
packages/mcpre-exportsAccessTokenfromkeycardai.oauth.server.verifier, so its verifier surface picks up the fields automatically. Its@grantpath readsrequest.user(the starletteKeycardUser) into a dict ofaccess_token,zone_idandresource_server_urlfor token exchange rather than wrapping or re-exportingKeycardUser, so nothing there needed extending.packages/a2apropagates theKeycardUserobject itself and inherits the fields.Gates: oauth, starlette and a2a suites green; ruff clean;
tydiagnostics unchanged from main;changelog.py validatepasses. Two mcp integration tests (test_auth_provider_init_with_zone_id,test_no_application_credential_does_enable_registration) fail identically on main in this environment and are unrelated.The keycard-sdk-spec divergence row is left as is until all three languages land.
Link to Devin session: https://app.devin.ai/sessions/bbc062cf32b548ef9c4b8b444d2ec67f
Open in Devin Desktop: https://app.devin.ai/desktop/session/bbc062cf32b548ef9c4b8b444d2ec67f?variant=devin
Requested by: @Larry-Osakwe