Skip to content

Developer

OAuth authorization

How Reqio's MCP server uses OAuth 2.1 with PKCE to authorize AI agents, issue opaque tokens, and enforce per-project audience binding.

The Reqio MCP server uses OAuth 2.1 with PKCE (RFC 7636 S256) to authorize agent connections. There are no API keys to paste. The agent discovers all endpoint URLs automatically via standard discovery documents, registers itself, and opens a browser consent screen where you approve the scopes you want to grant.

Access tokens are opaque and database-backed, not JWTs. Every token is audience-bound to a single project. The server re-reads live project membership on every tool call, so revoking a team member's access takes effect immediately without any token revocation step.

Discovery

MCP clients discover the authorization server and resource server metadata from two well-known documents before any other step.

Authorization server metadata (RFC 8414)

GET /.well-known/oauth-authorization-server HTTP/1.1
Host: reqio.app

Response (cached for 1 hour):

{
  "issuer": "https://reqio.app",
  "authorization_endpoint": "https://reqio.app/api/oauth/authorize",
  "token_endpoint": "https://reqio.app/api/oauth/token",
  "registration_endpoint": "https://reqio.app/api/oauth/register",
  "revocation_endpoint": "https://reqio.app/api/oauth/revoke",
  "scopes_supported": [
    "backlog:read",
    "status:write",
    "notes:write",
    "comments:write",
    "comments:delete",
    "features:delete",
    "conversations:read",
    "conversations:write",
    "broadcasts:write",
    "widget:read",
    "widget:write",
    "members:read",
    "members:write",
    "members:delete",
    "project:write"
  ],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}

Protected resource metadata (RFC 9728)

GET /.well-known/oauth-protected-resource HTTP/1.1
Host: reqio.app

Response:

{
  "resource": "https://reqio.app",
  "authorization_servers": ["https://reqio.app"],
  "scopes_supported": ["backlog:read", "..."],
  "bearer_methods_supported": ["header"]
}

Because the Authorization Server and Resource Server are co-located, both resource and authorization_servers[0] point to the same origin.

When a tool call arrives without a valid token, the server responds:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="reqio",
  resource_metadata="https://reqio.app/.well-known/oauth-protected-resource"

This WWW-Authenticate header is the signal MCP clients use to locate the discovery document and begin the authorization flow automatically.

Dynamic Client Registration (RFC 7591)

MCP agents register themselves dynamically before initiating authorization. Registration is open (no approval queue) and rate-limited.

POST /api/oauth/register HTTP/1.1
Host: reqio.app
Content-Type: application/json

{
  "redirect_uris": ["http://localhost:PORT/callback"],
  "client_name": "My Agent"
}

The response includes a client_id that the agent uses in subsequent requests. No client_secret is issued; Reqio uses public clients only (token_endpoint_auth_methods_supported: ["none"]).

The registered client is inert until a human approves a consent screen. The consent screen shows the client_id and the redirect host, not the self-declared client_name, so you can see exactly what is being connected.

Authorization code flow

1 -- Build the authorization URL

The agent redirects a browser to:

https://reqio.app/api/oauth/authorize
  ?response_type=code
  &client_id=CLIENT_ID
  &redirect_uri=http%3A%2F%2Flocalhost%3APORT%2Fcallback
  &scope=backlog%3Aread+status%3Awrite
  &state=RANDOM_STATE
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256

PKCE S256 is required. code_challenge is BASE64URL(SHA256(code_verifier)) with no padding. The state parameter should be a random value the agent verifies on return.

The Reqio consent screen shows:

  • The project name.
  • The client ID and redirect host.
  • Each requested scope in plain English, with a description of what it allows.

Scopes that exceed your plan entitlement or your role in the project are blocked and will not appear. You cannot accidentally grant more authority than you hold.

3 -- Exchange the code for tokens

After approval, the browser is redirected to the agent's redirect_uri with code and state parameters. The agent exchanges the code at the token endpoint:

POST /api/oauth/token HTTP/1.1
Host: reqio.app
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=http%3A%2F%2Flocalhost%3APORT%2Fcallback
&client_id=CLIENT_ID
&code_verifier=CODE_VERIFIER

The endpoint also accepts application/json. On success:

{
  "access_token": "OPAQUE_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "OPAQUE_REFRESH_TOKEN",
  "scope": "backlog:read status:write"
}

Authorization codes are single-use and short-lived (approximately 60 seconds). PKCE verification is performed on every exchange; a mismatched verifier is rejected.

Token properties

Access tokens are:

  • Opaque -- a cryptographically random value, base64url-encoded. The raw value is never stored; only its SHA-256 hash is kept.
  • Database-backed -- the server looks up the hash on every call. There is no offline verification.
  • Audience-bound -- each token stores the project resource URL (https://reqio.app/p/{projectId}) as its audience. A token presented to a different project's endpoint is rejected with TOKEN_AUDIENCE_MISMATCH.
  • Short-lived -- access tokens expire after approximately 1 hour.
  • Paired with a rotating refresh token -- refresh tokens are valid for approximately 30 days.

Token refresh

POST /api/oauth/token HTTP/1.1
Host: reqio.app
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=CLIENT_ID

Each refresh issues a new access token and a new refresh token. The old refresh token is immediately revoked. If a previously rotated refresh token is presented again (reuse detection), the server revokes the entire successor chain as a compromise signal.

Token revocation (RFC 7009)

POST /api/oauth/revoke HTTP/1.1
Host: reqio.app
Content-Type: application/x-www-form-urlencoded

token=TOKEN_TO_REVOKE
&client_id=CLIENT_ID

The endpoint always returns 200, even for unknown tokens, to avoid leaking whether a given token exists. Revocation deletes the token row; the agent loses access on the next tool call.

You can also revoke any active grant from Dashboard → Settings → Connected apps by clicking Revoke next to the connection.

Scopes

Each scope maps to a required OAuth 2.1 scope string and an internal capability from the project permission matrix. The server checks both independently on every call.

Read-only scopes are available on all Reqio plans:

| Scope | What it grants | |---|---| | backlog:read | Read feature requests, stats. | | conversations:read | List and read the private inbox. | | widget:read | Read widget and branding config. | | members:read | List project members and invitations. |

Write scopes require mcpWrite: true on the project owner's plan (Pro or Business):

| Scope | What it grants | |---|---| | status:write | Change request status. | | notes:write | Set or clear internal developer notes. | | comments:write | Post comments on requests. | | comments:delete | Permanently delete comments. | | features:delete | Permanently delete feature requests. | | conversations:write | Reply to inbox threads; convert reports to features. | | broadcasts:write | Send project-wide announcements. | | widget:write | Update widget and branding config. | | members:write | Invite members (owner only). | | members:delete | Remove members (owner only). | | project:write | Rename the project. |

Authority model

Token scopes define what an agent is permitted to attempt. The project permission matrix (assertCan) defines whether the acting user actually holds that permission. Both checks must pass on every call.

Granting members:write to an agent acting as a DEVELOPER-role member will be denied at the capability check, even if the scope was consented, because only OWNER-role members can invite new members. Removing a member from the project has the same immediate effect on their agent: the next tool call fails the assertCan check and returns FORBIDDEN, without any explicit token revocation step.