OAuth 2.0 Integration Overview
36Blocks can act as an OAuth 2.0 authorization server for your Block, letting third-party applications request delegated, scoped access to your users' accounts. It's the same shape as "Connect to GitHub" or "Sign in with Google", but for your own product, under your own control.
You decide which third-party applications are allowed to connect, and exactly what each one can access. Your user is always the one who approves or denies the request, on a consent screen you host yourself, and can revoke that access at any time.
What you can build with it
Partner and marketplace integrations: let other products connect to a user's account on your platform and read or act on their data, without ever seeing their credentials.
Your own first-party apps: a mobile app, CLI, or separate web app that needs to act on behalf of a logged-in user, authenticated the same standards-based way as any external integration.
Scoped, revocable access: grant a connecting app only what it actually needs (built-in identity fields, or custom scopes specific to your own product), and cut off access instantly, per connection or per integration.
An ecosystem around your product: once this is set up, any developer can build against it using standard OAuth 2.0 (authorization code + PKCE, refresh tokens, a userinfo endpoint) without needing anything custom from you beyond registering their app.
The four roles
| OAuth role | Who this is |
|---|---|
| Resource Owner | Your end user (the person who has an account on your Block). |
| Authorization Server | 36Blocks. We authenticate your user, run the consent screen, and issue the access token. |
| Resource Server | Your own platform or API. A third-party app uses the access token we issue as its credential when calling your APIs. |
| Client | The third-party application requesting access to your user's data. |
You control which third-party apps are allowed to request access (by registering each one as an OAuth Config, also called an OAuth client), and which pieces of data they can ask for (via scopes).
Key concepts
OAuth Config: one registered third-party integration for your Block. Has a client_id (public identifier) and, for confidential clients, a client_secret. You create and manage these from your Block's dashboard, or with the OAuth Clients endpoints in the Blocks module.
Client type: public or confidential, chosen once when you create the config and not changeable afterward.
Confidential clients (a server-side backend that can keep a secret) get a
client_secretand authenticate with it.Public clients (a mobile app or single-page app that can't safely hold a secret) have no secret and must use PKCE instead.
PKCE: a proof-of-possession mechanism that protects the authorization code from being intercepted and used by someone other than the app that started the flow. Always required for public clients, optional (but available) for confidential ones. RFC 7636 is the reference.
Scopes: what a connected app is allowed to access. Two are built in:
| Scope | Grants |
|---|---|
profile |
Name, mobile number, profile picture URL |
email |
Email address |
You can also define your own custom scopes (for example tickets:read) for anything specific to your own product. 36Blocks treats these as opaque strings. It's up to your own backend to enforce what a custom scope allows, by checking the token's granted scopes (with the Get OAuth User Info endpoint) before serving the data it's meant to gate.
If a connecting app doesn't send any scopes at all, the flow still completes normally. The app gets no profile or email data and no custom scopes, only a stable user identifier.
The flow, end to end
You set up an OAuth Config and (optionally) custom scopes from your Block's dashboard. See Dashboard setup.
You host a consent screen, a page on your own domain where you embed the 36Blocks widget. The third-party app redirects your user's browser there with its OAuth request appended to the URL. See Consent screen. The widget uses the Validate Authorization Request and Submit Authorization Decision endpoints behind the scenes.
Your user logs in (or signs up) and approves or denies the request, using the exact same login UI your users already see today.
The widget redirects the browser back to the third-party app's own
redirect_uri, with a one-time authorization code attached (or anerrorif denied).The third-party app's backend exchanges that code for an access token and refresh token with the Exchange OAuth Token endpoint (
POST /oauth/token).The third-party app calls Get OAuth User Info (
GET /oauth/userinfo) to get the user's identity and confirm exactly which scopes were granted.The third-party app refreshes the access token as needed, using the same Exchange OAuth Token endpoint with
grant_type=refresh_token.Either your user or you can revoke access at any time. See Revoking access below.
Steps 1 and 2 are things you do once per integration. Steps 3 to 8 happen through the widget and the APIs documented in this module; the third-party app's own developer implements steps 4 to 7 against the endpoint pages.
Using the endpoints
Token requests come from a backend, never from a browser, and the response is plain JSON without the standard envelope.
The access token goes in the
proxy_auth_tokenheader, notAuthorizationand notBearer-prefixed.Authorization codes are single use. Presenting an already-used code, or an already-rotated-out refresh token, revokes the whole connection, since it is treated as a sign the credential may have leaked. The connecting app must send the user through consent again.
Refresh tokens rotate on every use. Each refresh returns a new
access_tokenand a newrefresh_token, and the one just used stops working. A refresh token has no expiry timer; it only stops working if the connection is revoked.Errors from the token endpoint are
{ "error": "invalid_grant", "error_description": "..." }with a 4xx status.invalid_grantis deliberately generic: a wrong secret and a failed PKCE check return the same error.Checking custom scopes: call Get OAuth User Info with the token you were handed and check
granted_scopes. If no scopes were granted the response is still a normal200with an emptygranted_scopes.
Revoking access
A user revokes their own connection with the Revoke OAuth Grant endpoint (
POST /oauth/revoke), called with that user's ownproxy_auth_tokenfrom a "connected apps" screen on your own site, not by the third-party app. The connection's access token then returns401and its refresh token returnsinvalid_grant.There is no endpoint to list a user's connected apps or grant IDs. If you build a "connected apps" screen, record each
grant_idyourself when your backend learns about a new connection.You revoke every connection for an integration at once by disabling its OAuth Config (see Dashboard setup, or the Disable OAuth Client endpoint). Disabling immediately revokes every active connection made through that config.