-
Notifications
You must be signed in to change notification settings - Fork 1
docs: add OpenAPI specs for auth service public endpoints #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| openapi: 3.1.0 | ||
| info: | ||
| title: Versola Auth — Token Introspection Endpoint | ||
| version: 1.0.0 | ||
| description: | | ||
| OAuth 2.0 Token Introspection (RFC 7662). Lets an authenticated client or resource server | ||
| ask whether a token is currently active and retrieve its metadata. | ||
|
|
||
| Both access tokens (JWTs) and refresh tokens (opaque) are accepted; the token format is | ||
| detected automatically, so `token_type_hint` is unnecessary. | ||
|
|
||
| A token that cannot be verified, has expired, was revoked, or does not belong to the | ||
| calling client is reported as `{"active": false}` rather than as an error — the caller is | ||
| never told why a token is not active. | ||
| license: | ||
| name: Business Source License 1.1 | ||
| url: https://github.com/versolauth/versola/blob/main/LICENCE.md | ||
|
|
||
| servers: | ||
| - url: https://auth.example.com | ||
| description: Auth service issuer origin | ||
|
|
||
| paths: | ||
| /introspect: | ||
| post: | ||
| operationId: introspect | ||
| summary: Introspect a token | ||
| tags: [Token] | ||
| security: | ||
| - clientSecretBasic: [] | ||
| - clientSecretPost: [] | ||
| requestBody: | ||
| required: true | ||
| content: | ||
| application/x-www-form-urlencoded: | ||
| schema: | ||
| $ref: '#/components/schemas/IntrospectionRequest' | ||
| example: | ||
| token: eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9... | ||
| responses: | ||
| '200': | ||
| $ref: '#/components/responses/Introspection' | ||
| '400': | ||
| $ref: '#/components/responses/InvalidRequest' | ||
| '401': | ||
| $ref: '#/components/responses/InvalidClient' | ||
|
|
||
| components: | ||
| securitySchemes: | ||
| clientSecretBasic: | ||
| type: http | ||
| scheme: basic | ||
| description: '`client_secret_basic` — `client_id` as the username, `client_secret` as the password.' | ||
| clientSecretPost: | ||
| type: apiKey | ||
| in: cookie | ||
| name: client_secret | ||
| description: | | ||
| `client_secret_post` — `client_id` and `client_secret` sent as form fields. Modelled | ||
| here as a scheme only because OpenAPI has no dedicated form-credential type. | ||
|
|
||
| schemas: | ||
| IntrospectionRequest: | ||
| type: object | ||
| required: [token] | ||
| properties: | ||
| token: | ||
| type: string | ||
| description: | | ||
| The token to introspect. A value in JWT form is treated as an access token, | ||
| anything else as an opaque refresh token. | ||
| client_id: | ||
| type: string | ||
| description: Required for `client_secret_post`. | ||
| client_secret: | ||
| type: string | ||
| description: Confidential clients using `client_secret_post`. | ||
|
|
||
| IntrospectionResponse: | ||
| type: object | ||
| required: [active] | ||
| description: | | ||
| Only `active` is guaranteed. When `active` is `false` every other member is absent. | ||
| properties: | ||
| active: | ||
| type: boolean | ||
| client_id: | ||
| type: string | ||
| description: Client the token was issued to. | ||
| scope: | ||
| type: string | ||
| description: Space-delimited granted scopes. | ||
| username: | ||
| type: string | ||
| token_type: | ||
| type: string | ||
| description: '`Bearer` for access tokens.' | ||
| exp: | ||
| type: integer | ||
| format: int64 | ||
| description: Expiry, seconds since the epoch. | ||
| iat: | ||
| type: integer | ||
| format: int64 | ||
| description: Issued at, seconds since the epoch. | ||
| nbf: | ||
| type: integer | ||
| format: int64 | ||
| description: Not valid before, seconds since the epoch. | ||
| sub: | ||
| type: string | ||
| description: User id, or the `client_id` for a `client_credentials` token. | ||
| aud: | ||
| type: array | ||
| items: | ||
| type: string | ||
| iss: | ||
| type: string | ||
| jti: | ||
| type: string | ||
|
|
||
| ErrorResponse: | ||
| type: object | ||
| required: [error] | ||
| properties: | ||
| error: | ||
| type: string | ||
| enum: [invalid_request, invalid_client] | ||
| error_description: | ||
| type: string | ||
|
|
||
| responses: | ||
| Introspection: | ||
| description: | | ||
| Introspection result. Returned with `200` whether or not the token is active. | ||
| headers: | ||
| Cache-Control: | ||
| schema: | ||
| type: string | ||
| const: no-store | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When JWT deserialization fails, Severity: medium 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage. |
||
| Pragma: | ||
| schema: | ||
| type: string | ||
| const: no-cache | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/IntrospectionResponse' | ||
| examples: | ||
| active: | ||
| summary: Active access token | ||
| value: | ||
| active: true | ||
| client_id: web-app | ||
| scope: openid profile email | ||
| token_type: Bearer | ||
| exp: 1735689600 | ||
| iat: 1735686000 | ||
| sub: 018f3c6e-5a2b-7c11-9d0e-4f8a2b6c1d33 | ||
| aud: [https://api.example.com] | ||
| iss: https://auth.example.com | ||
| jti: 9x8VJ0mQ0iKQxRZ8m6bTsw | ||
| inactive: | ||
| summary: Unknown, expired, revoked, or another client's token | ||
| value: | ||
| active: false | ||
|
|
||
| InvalidRequest: | ||
| description: The body is not a readable form, or the `token` field is missing. | ||
| headers: | ||
| Cache-Control: | ||
| schema: | ||
| type: string | ||
| const: no-store | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/ErrorResponse' | ||
| example: | ||
| error: invalid_request | ||
| error_description: Request is missing a required parameter or is otherwise malformed | ||
|
|
||
| InvalidClient: | ||
| description: Client authentication failed. | ||
| headers: | ||
| Cache-Control: | ||
| schema: | ||
| type: string | ||
| const: no-store | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/ErrorResponse' | ||
| example: | ||
| error: invalid_client | ||
| error_description: Client authentication failed | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| openapi: 3.1.0 | ||
| info: | ||
| title: Versola Auth — JWKS Endpoint | ||
| version: 1.0.0 | ||
| description: | | ||
| JSON Web Key Set endpoint (RFC 7517, OpenID Connect Discovery §3). Publishes the public | ||
| keys that verify the signatures on tokens issued by this server. | ||
|
|
||
| Resource servers fetch this document to validate access tokens and ID tokens offline. The | ||
| `kid` in a token's JOSE header selects the key to verify it with. | ||
|
|
||
| The set contains the currently active signing key plus any keys still needed to verify | ||
| tokens issued before the last rotation, so a resource server that caches the document | ||
| keeps working across a rotation. Only public key material is exposed. | ||
|
|
||
| The response is cacheable for 24 hours. On encountering an unknown `kid`, refetch rather | ||
| than waiting for the cache to expire. | ||
| license: | ||
| name: Business Source License 1.1 | ||
| url: https://github.com/versolauth/versola/blob/main/LICENCE.md | ||
|
|
||
| servers: | ||
| - url: https://auth.example.com | ||
| description: Auth service issuer origin | ||
|
|
||
| paths: | ||
| /.well-known/jwks.json: | ||
| get: | ||
| operationId: jwks | ||
| summary: Retrieve the JSON Web Key Set | ||
| description: No authentication required. | ||
| tags: [Discovery] | ||
| security: [] | ||
| responses: | ||
| '200': | ||
| $ref: '#/components/responses/Jwks' | ||
|
|
||
| components: | ||
| schemas: | ||
| JsonWebKeySet: | ||
| type: object | ||
| required: [keys] | ||
| properties: | ||
| keys: | ||
| type: array | ||
| items: | ||
| $ref: '#/components/schemas/JsonWebKey' | ||
|
|
||
| JsonWebKey: | ||
| type: object | ||
| required: [kty, kid] | ||
| additionalProperties: true | ||
| properties: | ||
| kty: | ||
| type: string | ||
| description: Key type. | ||
| example: RSA | ||
| use: | ||
| type: string | ||
| description: Intended use; signature verification. | ||
| const: sig | ||
| kid: | ||
| type: string | ||
| description: Key identifier, matched against the `kid` in a token's JOSE header. | ||
| alg: | ||
| type: string | ||
| description: Signing algorithm the key is used with. | ||
| example: RS256 | ||
| n: | ||
| type: string | ||
| description: RSA modulus, base64url-encoded. | ||
| e: | ||
| type: string | ||
| description: RSA public exponent, base64url-encoded. | ||
|
|
||
| responses: | ||
| Jwks: | ||
| description: The current key set. | ||
| headers: | ||
| Cache-Control: | ||
| schema: | ||
| type: string | ||
| const: public, max-age=86400 | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/JsonWebKeySet' | ||
| example: | ||
| keys: | ||
| - kty: RSA | ||
| use: sig | ||
| kid: 2026-08-01 | ||
| alg: RS256 | ||
| n: 0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4... | ||
| e: AQAB |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extractCredentialsreadsclient_secret_postexclusively from the parsed form; it never reads aclient_secretcookie. Declaring this as a cookieapiKeycauses OpenAPI tooling to send the secret in a location the endpoint ignores, despite the body schema description. Other locations where this applies:auth/open-api/par.yaml:72,auth/open-api/revoke.yaml:55,auth/open-api/token.yaml:85.Severity: medium
Other Locations
auth/open-api/par.yaml:72auth/open-api/revoke.yaml:55auth/open-api/token.yaml:85🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.