OAuth 2.1

Summary

Benevia Core includes a complete OAuth 2.1 authorization server. It lets OAuth-capable clients — such as Claude and ChatGPT custom connectors — get delegated, scoped access to your API on behalf of a signed-in user. Clients register themselves, the user approves access on a consent screen your app hosts, and the client receives tokens capped to the access level the user chose.

Key properties:

  • Stateless client registration — the client_id is the registration data, HMAC-signed by the server. Nothing is stored, so any client can self-register without an admin step.
  • PKCE everywhere — all clients are public clients; authorization code + PKCE is the only interactive flow.
  • Multi-tenant without tenant URLs — endpoints are rootless (/connect/...); the tenant travels inside the authorization code and token claims.
  • Scoped access — the user grants read, read+write, or full access per connection. The cap is enforced on every OData request and MCP tool call, on top of the user's own RBAC permissions.

How a client connects

Discovery → Registration → Authorize → Sign-in + Consent → Code → Tokens → Bearer requests
  1. The client reads /.well-known/oauth-protected-resource and /.well-known/openid-configuration to find the endpoints.
  2. It registers itself at POST /connect/register and receives a self-encoded client_id.
  3. It opens GET /connect/authorize in the user's browser. Core redirects to your app's consent page.
  4. The user signs in and approves an access level. Your app records the choice via POST api/oauth/consent/approve.
  5. The browser returns to /connect/authorize; Core issues an authorization code bound to the user's tenant.
  6. The client exchanges the code (plus its PKCE verifier) at POST /connect/token for access and refresh tokens.
  7. The client calls /mcp and the OData API with Authorization: Bearer <access token>.

Endpoints

Endpoint Method Purpose
/.well-known/openid-configuration GET Authorization-server discovery document
/.well-known/oauth-protected-resource GET Protected-resource metadata for MCP clients (mapped by UseCoreMcp())
/connect/register POST Dynamic Client Registration (anonymous)
/connect/authorize GET Authorization endpoint — consent and code issuance
/connect/token POST Token endpoint — code exchange and refresh
/connect/revoke POST Token revocation
api/oauth/consent/info GET Client name and scope descriptions for your consent UI
api/oauth/consent/approve POST Records the user's consent choice
api/oauth/connected-apps GET The current user's connected apps
api/oauth/connected-apps/{grantId}/disconnect POST Revokes a connection and all its tokens

Scopes

Scope Grants
benevia.read Read data
benevia.write Create and update data
benevia.delete Delete data
offline_access Refresh tokens (plumbing — not shown to users)

The data scopes form a per-connection access ceiling: the highest granted scope decides the level (delete → full, write → read+write, otherwise read-only). The ceiling only caps what the user could already do through RBAC — it never adds access. Writes and deletes above the ceiling are rejected on both the OData API and MCP tools.

Guides

Guide Covers
Setup Wiring AddCoreOAuth into your host and the required configuration
Consent page The consent UI contract your client app must implement
Connected apps Letting users list and disconnect their connections
MCP connectors Connecting Claude / ChatGPT end to end, and troubleshooting

Notes

  • Token and grant state (codes, refresh tokens, grants) is stored per tenant; client registrations are never stored.
  • A user can connect the same client several times; each connection is an independent grant.
  • See Expose entities via MCP for the MCP server itself and Use your API for JWT sign-in used by first-party clients.