OAuth for custom clients
If you're building a custom client or integrating Gurita into another platform, use the standard OAuth 2.1 authorization code flow with PKCE to obtain an access token. Your client registers dynamically, redirects the user to Gurita to sign in, and receives an access token good for 180 days.
Discovery
Two standard RFC 8414 / RFC 9728 discovery endpoints tell your client where to go:
- Authorization server metadata:
/.well-known/oauth-authorization-server - Protected resource metadata:
/.well-known/oauth-protected-resource
Both return JSON describing the endpoints, supported grant types, and scopes.
Example:
curl https://YOUR-DOMAIN/.well-known/oauth-authorization-serverThis returns endpoints like:
{
"issuer": "https://YOUR-DOMAIN",
"authorization_endpoint": "https://YOUR-DOMAIN/api/mcp/oauth/authorize",
"token_endpoint": "https://YOUR-DOMAIN/api/mcp/oauth/token",
"registration_endpoint": "https://YOUR-DOMAIN/api/mcp/oauth/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"scopes_supported": ["reports:read"]
}Dynamic client registration (RFC 7591)
Register your client without pre-approval:
curl -X POST https://YOUR-DOMAIN/api/mcp/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My Custom Client",
"redirect_uris": ["https://example.com/callback"]
}'The server returns a client_id (and echoes your name and redirect URIs). There is no client secret — all clients are public clients (token_endpoint_auth_method: none) and prove themselves with PKCE instead. Keep the client_id; you need it for the authorization flow.
Redirect URIs must be:
- HTTPS, except for
localhostand127.0.0.1, which may use HTTP on any port. - Registered up front. The
redirect_uriyou send later must exactly match one you registered.
Authorization code flow with PKCE
1. Generate PKCE challenge
Create a code verifier (random 43–128 characters of unreserved characters: A-Z, a-z, 0-9, -, ., _, ~):
code_verifier = base64url(randomBytes(32))Hash it with SHA-256 and base64url-encode:
code_challenge = base64url(sha256(code_verifier))2. Redirect to authorization endpoint
Redirect the user to:
https://YOUR-DOMAIN/api/mcp/oauth/authorize?
response_type=code&
client_id=<your-client-id>&
redirect_uri=https://example.com/callback&
code_challenge=<code-challenge>&
code_challenge_method=S256&
state=<optional-state-value>Gurita shows a sign-in page that names your client and the host the code will be sent to. The user signs in with their Gurita email and password and clicks Sign in & allow. The server redirects to your redirect_uri with an authorization code:
https://example.com/callback?code=<code>&state=<your-state-value>Verify the state matches your request to prevent CSRF.
3. Exchange code for token
curl -X POST https://YOUR-DOMAIN/api/mcp/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "<code-from-redirect>",
"client_id": "<your-client-id>",
"redirect_uri": "https://example.com/callback",
"code_verifier": "<code-verifier>"
}'The server returns:
{
"access_token": "gmcp_...",
"token_type": "Bearer",
"expires_in": 15552000,
"scope": "reports:read"
}The access_token is a 180-day MCP token (gmcp_ prefix). Use it in an Authorization: Bearer header on all tool calls.
Token behavior
- Lifetime: 180 days from issuance (see
expires_infor the exact duration in seconds). - No refresh: The token does not renew automatically. When it expires, redirect the user to authorize again.
- Single-use codes: Authorization codes expire in 10 minutes and can be redeemed exactly once.
- Listed and revocable: every issued token appears in the token list on Settings → AI Assistant (MCP), named
OAuth: <client name>, where it can be revoked.
Scopes
Currently, one scope is defined:
reports:read— Read access to all MCP tools (default if no scope is specified).
The scope is cosmetic; the token user's permissions determine what tools actually work.
Security considerations
- PKCE is mandatory — your client must implement S256; S256 is the only supported method.
- Redirect URI validation — URIs must be HTTPS (except
http://localhostandhttp://127.0.0.1). Mismatches fail the authorization code exchange. - Public clients only — there is no client secret; PKCE protects the code exchange.
- User permissions — the token inherits the user's Gurita permissions. Restricted users and asset limitations apply.
- Token storage — the token is returned only once, in the token response. Store it securely (encrypted config, environment variable or credential manager). Gurita keeps only a hash and cannot show it again.
- Revocation — users can revoke tokens on Settings → AI Assistant (MCP) anytime.
Error responses
Errors from the register, authorize and token endpoints come back as 400 responses with a standard OAuth error body:
{ "error": "invalid_grant", "error_description": "Authorization code is invalid, expired or already used." }error | Cause |
|---|---|
invalid_redirect_uri | Registration: a redirect URI isn't HTTPS (or localhost). |
invalid_client | Unknown client_id. |
invalid_request | A required parameter is missing or malformed, or redirect_uri doesn't match a registered one. |
unsupported_response_type | response_type isn't code. |
unsupported_grant_type | grant_type isn't authorization_code. |
invalid_grant | The code is invalid, expired, already used, or the PKCE verifier doesn't match. |
