Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/claims-contract.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ RFC 9068 JWT access token。provider は `generateToken`(auth.provider の `pa
| `azp` | *認証済み* client id であり、body 生の `client_id` ではない(`oauth/src/grants/authorization.mts` の D-6)。 | `PayloadSubjectIdCollector` → `ATTR_CLIENT_ID`。 | token がどの client 経由で発行されたか。 |
| `aud` | RFC 8707 `resource` パラメータを audience として反響。refresh でパラメータが繰り返されなければ落ちる(§2.2)。 | jose 検証でピン — `oauth.jwt.audience` / `OAUTH_JWT_AUDIENCE`。 | token の宛先 resource server。E2E 値 `https://api.e2e.test` が `tests/provider/clients.yaml`(`allowedAudiences`)**と** verifier の env の両方に現れるのはこのため。 |
| `iss` | デプロイ設定の issuer。必須、リクエスト由来にしない。 | jose 検証でピン — `oauth.jwt.issuer` / `OAUTH_JWT_ISSUER`。 | デプロイの identity。 |
| `typ`(header) | access token は `at+jwt`(RFC 9068)、refresh token は `rt+jwt`、ID token(wire 名 `id_token`)は `id+jwt`| `oauth.jwt.tokenType`、default `at+jwt`。比較時に `application/` prefix は無視。 | 3 種の token を区別する **唯一の** 判別子 — このピンが、同じ鍵で署名された refresh / id token が `/verify` を通ることを防いでいる。claim レベルの代替チェックは存在しない。 |
| `typ`(header) | access token は `at+jwt`(RFC 9068)、refresh token は `rt+jwt`、ID token(wire 名 `id_token`)は auth.provider v0.10.0 以降は標準の `JWT`(それ以前は非標準の `id+jwt` で、`typ` を厳格に検証する外部 RP に弾かれていた)。検証側は auth.provider#402 が閉じるまでの移行期間、両方の綴りを受理する。 | `oauth.jwt.tokenType`、default `at+jwt`。比較時に `application/` prefix は無視。 | 3 種の token を区別する **唯一の** 判別子 — このピンが、同じ鍵で署名された refresh / id token が `/verify` を通ることを防いでいる。防いでいる実体は **`at+jwt` と互いに素であること** であり、`JWT` は `id+jwt` と同様にこれを満たす。claim レベルの代替チェックは存在しない。 |
| `exp` / `iat` | 常に刻印(`core/src/grants/token.mts`)。 | 両方必須(`tokenAuthenticator` の必須 claim チェック + 常設の `maxTokenAgeSeconds` 上限)。`exp` のない token は永続 credential として拒否。 | 寿命。verifier は issuer の規律を信頼せず、issuer の `exp` をさらに上限で抑える。 |

## 境界を越えない claim
Expand Down
2 changes: 1 addition & 1 deletion docs/claims-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Path convention for the tables below: paths in the *Provider writes* column are
| `azp` | The *authenticated* client id, not the raw body `client_id` (D-6 in `oauth/src/grants/authorization.mts`). | `PayloadSubjectIdCollector` → `ATTR_CLIENT_ID`. | Which client the token was issued through. |
| `aud` | The RFC 8707 `resource` parameter echoed back as the audience; dropped on refresh when the parameter is not repeated (§2.2). | Pinned by jose verification — `oauth.jwt.audience` / `OAUTH_JWT_AUDIENCE`. | The resource server the token is addressed to. The E2E value `https://api.e2e.test` appears in `tests/provider/clients.yaml` (`allowedAudiences`) *and* the verifier's env for exactly this reason. |
| `iss` | Deployment-configured issuer; required, never request-derived. | Pinned by jose verification — `oauth.jwt.issuer` / `OAUTH_JWT_ISSUER`. | Deployment identity. |
| `typ` (header) | `at+jwt` on access tokens (RFC 9068), `rt+jwt` on refresh tokens, `id+jwt` on ID tokens (wire name `id_token`). | `oauth.jwt.tokenType`, default `at+jwt`; an `application/` prefix on either side is ignored when comparing. | The **only** discriminator between the three token kinds — the pin is what keeps a refresh or id token signed with the same key from passing `/verify`. There is no claim-level fallback check. |
| `typ` (header) | `at+jwt` on access tokens (RFC 9068), `rt+jwt` on refresh tokens, and the standard `JWT` on ID tokens (wire name `id_token`) since auth.provider v0.10.0 — it was the nonstandard `id+jwt` before, which strict external RPs rejected; verification accepts both spellings during the window auth.provider#402 closes. | `oauth.jwt.tokenType`, default `at+jwt`; an `application/` prefix on either side is ignored when comparing. | The **only** discriminator between the three token kinds — the pin is what keeps a refresh or id token signed with the same key from passing `/verify`. What does the keeping-out is being **disjoint from `at+jwt`**, which `JWT` satisfies exactly as `id+jwt` did. There is no claim-level fallback check. |
| `exp` / `iat` | Always stamped (`core/src/grants/token.mts`). | Both required (`tokenAuthenticator`'s required-claims check + the always-set `maxTokenAgeSeconds` bound); a token without `exp` is refused as a permanent credential. | Lifetime. The verifier additionally caps the issuer's `exp` rather than trusting issuer discipline. |

## Claims that do NOT cross
Expand Down
5 changes: 4 additions & 1 deletion tests/abac/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ describe('ABAC: only access tokens are decision inputs', () => {

it('rejects the id_token from the same grant', async () => {
const res = await verify({ token: projectGrant.id_token });
expect(decodeJwt(projectGrant.id_token).header.typ).toBe('id+jwt');
// `JWT` since auth.provider#394 (v0.10.0). What keeps an id_token out of
// `/verify` is being disjoint from `at+jwt`, which the standard value
// satisfies identically — so the refusal below is unchanged.
expect(decodeJwt(projectGrant.id_token).header.typ).toBe('JWT');
expect(res.status).toBe(401);
expect(res.body.decision).toBe('deny');
expect(res.body.code).toBe('invalid_token');
Expand Down
10 changes: 9 additions & 1 deletion tests/token-flow/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,15 @@ describe('Real grant path: login -> /authorize (PKCE) -> /token', () => {
// (see the negative tests in tests/abac). Pinning all three here means
// a provider-side change to any of them fails on this repo's CI rather
// than silently widening what /verify accepts.
expect(decodeJwt(grant.id_token).header.typ).toBe('id+jwt');
//
// The id_token stamps the standard `JWT` as of auth.provider#394
// (v0.10.0): the nonstandard `id+jwt` failed strict external RPs and
// bought nothing, because what keeps an id_token out of `/verify` is
// being disjoint from RFC 9068's `at+jwt` — which `JWT` satisfies just
// as well. Verification accepts both spellings during the migration
// window auth.provider#402 closes; this pins what the provider MINTS,
// so it is the standard value with no dual accept.
expect(decodeJwt(grant.id_token).header.typ).toBe('JWT');
expect(decodeJwt(grant.refresh_token).header.typ).toBe('rt+jwt');
});

Expand Down
Loading