Skip to content
Bondry

OAuth 2.1 for apps

Authorization code with PKCE, rotating refresh tokens, scopes and the endpoints an app calls.

API keys cover server-to-server integrations. OAuth is for an app that acts on behalf of a member, with that member's consent: a mobile app, or a third party tool a member connects to their account.

Flows#

  • Authorization code with PKCE (S256), required for every client. Public clients such as mobile apps carry no secret; confidential clients may have one.
  • Refresh tokens rotate. Each use issues a new one and invalidates the previous. Reusing a rotated token revokes the entire family, which is how a stolen token is detected.
  • No implicit flow, no password grant. Device authorization is not in v1.

Endpoints#

Endpoint What it does
GET /oauth/authorize The consent screen, in the member's session, listing the scopes in plain language
POST /oauth/token authorization_code and refresh_token grants
POST /oauth/revoke Revokes a token
GET /oauth/userinfo Minimal profile, according to the granted scopes
GET /.well-known/oauth-authorization-server Metadata, RFC 8414

Token lifetimes#

Artefact Lifetime Notes
Authorization code 60 seconds Single use
Access token 1 hour Opaque, not a JWT; stored as a SHA-256 hash
Refresh token 30 days Rotating

Access tokens are opaque on purpose. A JWT that cannot be revoked before it expires is the wrong trade for an app that a member may disconnect at any moment.

Redirect URIs#

Registered per client and compared exactly. Custom schemes (bondry-app://callback) and loopback (http://127.0.0.1:<port>) are allowed for native apps; localhost is accepted only in a local environment.

Scopes#

The same scopes as the API keys, declared by the core and by each module: members.read, community.write, blog.read, messages.*, notifications.*, account.*, plus profile for minimal identity and offline_access for a refresh token.

The gate accepts a bearer token from OAuth or from an API key. An OAuth token carries the member, so the member's group permissions apply on top of the scope.

Warning A scope never widens what the member cannot do. Requesting community.write for a member who cannot post still fails.

Registering a client#

System > API > OAuth clients in the panel: name, logo, website, redirect URIs, and whether the client is confidential. The secret of a confidential client is shown once. Every change is written to the admin log.

What the member sees#

  • The consent screen, rendered in the site theme, with the app name and logo, the scopes explained, an explicit warning when the app is not first party, and both Allow and Deny.
  • Account > Connected apps, listing every authorised app with its scopes and last use, and a button that revokes the app and its whole token family.

Revocation cascades: banning or deleting a member, or revoking the client, revokes the tokens.

Implementation notes#

  • state is required and echoed back.
  • PKCE is required, with S256.
  • Hash comparisons are constant time.
  • /oauth/token is rate limited per client and per IP.
  • Tokens never appear in a log.

Every flow and every refusal has a test: wrong PKCE verifier, different redirect URI, reused code, reused rotated refresh token, ungranted scope, banned member.