OAuth2 Provider
Warning
The OAuth2 provider functionality is currently experimental and unstable. This feature:
- Is subject to breaking changes without notice
- May have incomplete functionality
- Is not recommended for production use
- Requires the
oauth2experiment flag to be enabled
Use this feature for development and testing purposes only.
Coder can act as an OAuth2 authorization server, allowing third-party applications to authenticate users through Coder and access the Coder API on their behalf. This enables integrations where external applications can leverage Coder's authentication and user management.
Requirements
- Admin privileges in Coder
- OAuth2 experiment flag enabled
- HTTPS recommended for production deployments
Enable OAuth2 Provider
Add the oauth2 experiment flag to your Coder server:
coder server --experiments oauth2
Or set the environment variable:
CODER_EXPERIMENTS=oauth2
Creating OAuth2 Applications
Method 1: Web UI
- Navigate to Deployment Settings > OAuth2 Applications.
- On the Applications tab, select Add application.
- Fill in the application details:
- Name: Your application name
- Callback URL:
https://yourapp.example.com/callback(web) ormyapp://callback(native/desktop) - Icon: Optional icon URL
Method 2: Management API
Create an application using the Coder API:
curl -X POST \
-H "Authorization: Bearer $CODER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "My Application",
"callback_url": "https://myapp.example.com/callback",
"icon": "https://myapp.example.com/icon.png"
}' \
"$CODER_URL/api/v2/oauth2-provider/apps"
Generate a client secret:
curl -X POST \
-H "Authorization: Bearer $CODER_SESSION_TOKEN" \
"$CODER_URL/api/v2/oauth2-provider/apps/$APP_ID/secrets"
Dynamic Client Registration
Dynamic Client Registration (RFC 7591) lets a client register itself against /oauth2/register instead of an admin creating the application manually. It's disabled by default; an owner must turn it on before any client can self-register.
Change the setting in the web UI:
- Navigate to Deployment Settings > OAuth2 Applications.
- Select the Settings tab.
- Select Enable or Disable next to Dynamic Client Registration.
Enabling asks you to confirm first.
Disabling does not.
The tab is linkable directly at https://$CODER_ACCESS_URL/deployment/oauth2-provider/apps?tab=settings.
Viewing the tab requires permission to view deployment configuration, and changing the setting requires permission to edit it. Without edit permission the button is present but inactive, and the page says why.
Check or change the setting with the CLI:
coder oauth2-provider dcr enable
coder oauth2-provider dcr disable
Or with the management API:
curl -X PUT \
-H "Authorization: Bearer $CODER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"dynamic_client_registration_enabled": true}' \
"$CODER_URL/api/v2/oauth2-provider/settings"
curl -H "Authorization: Bearer $CODER_SESSION_TOKEN" \
"$CODER_URL/api/v2/oauth2-provider/settings"
Disabling only blocks new self-registrations. Applications that already registered while it was enabled keep authorizing and exchanging tokens normally; disabling does not revoke or otherwise affect them.
Integration Patterns
Client Authentication Methods
Coder supports the following OAuth2 client authentication methods at the token endpoint (/oauth2/tokens):
client_secret_basic(recommended): HTTP Basic authentication (RFC 6749 §2.3.1). The username isclient_idand the password isclient_secret.client_secret_post: Form-based authentication whereclient_idandclient_secretare sent in the request body.none: No client secret. The client is a public client and authenticates with PKCE alone (RFC 7591 §2, OAuth 2.1 §2.1). Available only through Dynamic Client Registration, which is disabled by default, since a client's type is set when it registers and apps created through the admin UI or API are always confidential.
Note
Registration accepts none today, but the token endpoint does not yet
honor it: an authorization_code exchange still requires
client_secret, so a public client cannot obtain a token yet. Discovery
omits none from token_endpoint_auth_methods_supported for the same
reason, so a conforming client is not told to attempt an exchange that
would be rejected.
Coder supports both secret-based methods for compatibility; existing integrations using client_secret_post do not need to change.
Public clients suit native, mobile, and CLI applications that cannot keep a secret confidential. Note the redirect URI restrictions below before choosing one.
If you use Dynamic Client Registration (RFC 7591) and omit token_endpoint_auth_method, clients default to client_secret_basic. To request client_secret_post, set token_endpoint_auth_method to client_secret_post in the registration request. To register a public client, set it to none: Coder issues no client_secret, and the registration response omits that field entirely.
Important
A public client may use http:// only with a loopback host
(localhost, 127.0.0.1, [::1]). An http:// redirect URI to any
other host is rejected, so use https:// instead. A confidential
client has the same restriction but also accepts .localhost
subdomains over http://.
Which schemes a redirect URI may use is a separate restriction that also differs by client type. See Callback URL schemes.
If client authentication fails, the token endpoint returns HTTP 401 with an OAuth2 invalid_client error and a WWW-Authenticate: Basic realm="coder" response header.
Standard OAuth2 Flow
-
Authorization Request: Redirect users to Coder's authorization endpoint:
https://coder.example.com/oauth2/authorize? client_id=your-client-id& response_type=code& redirect_uri=https://yourapp.example.com/callback& state=random-string -
Token Exchange: Exchange the authorization code for an access token.
Option A: HTTP Basic authentication (
client_secret_basic, recommended)curl -X POST \ -u "$CLIENT_ID:$CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=$AUTH_CODE" \ -d "redirect_uri=https://yourapp.example.com/callback" \ "$CODER_URL/oauth2/tokens"Option B: Form parameters (
client_secret_post)curl -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=$AUTH_CODE" \ -d "client_id=$CLIENT_ID" \ -d "client_secret=$CLIENT_SECRET" \ -d "redirect_uri=https://yourapp.example.com/callback" \ "$CODER_URL/oauth2/tokens" -
API Access: Use the access token to call Coder's API:
curl -H "Authorization: Bearer $ACCESS_TOKEN" \ "$CODER_URL/api/v2/users/me"
Note
The PKCE flow below is the required integration path. The example
above is shown for reference but omits the mandatory code_challenge
parameter. See PKCE Flow for the complete flow.
PKCE Flow (Required)
PKCE is required for all OAuth2 authorization code flows. Coder enforces PKCE in compliance with the OAuth 2.1 specification. Both public and confidential clients must include PKCE parameters:
Note
code_verifier and code_challenge must each be 43-128 characters from
the unreserved character set [A-Za-z0-9-._~] (RFC 7636 §4.1). A value
outside these bounds is rejected with an invalid_request error, at the
token endpoint for code_verifier and at the authorization endpoint for
code_challenge.
-
Generate a code verifier and challenge:
CODE_VERIFIER=$(openssl rand -base64 96 | tr -d '\n' | tr '+/' '-_' | tr -d '=') CODE_CHALLENGE=$(echo -n $CODE_VERIFIER | openssl dgst -sha256 -binary | base64 | tr -d "=" | tr '+/' '-_') -
Include PKCE parameters in the authorization request:
https://coder.example.com/oauth2/authorize? client_id=your-client-id& response_type=code& code_challenge=$CODE_CHALLENGE& code_challenge_method=S256& redirect_uri=https://yourapp.example.com/callback -
Include the code verifier in the token exchange (see Client Authentication Methods):
curl -X POST \ -u "$CLIENT_ID:$CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=$AUTH_CODE" \ -d "code_verifier=$CODE_VERIFIER" \ -d "redirect_uri=https://yourapp.example.com/callback" \ "$CODER_URL/oauth2/tokens"
Discovery Endpoints
Coder provides OAuth2 discovery endpoints for programmatic integration:
- Authorization Server Metadata:
GET /.well-known/oauth-authorization-server - Protected Resource Metadata:
GET /.well-known/oauth-protected-resource
These endpoints return server capabilities and endpoint URLs according to RFC 8414 and RFC 9728.
Token Management
Refresh Tokens
Refresh an expired access token.
Option A: HTTP Basic authentication (client_secret_basic)
curl -X POST \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=$REFRESH_TOKEN" \
"$CODER_URL/oauth2/tokens"
Option B: Form parameters (client_secret_post)
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=$REFRESH_TOKEN" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
"$CODER_URL/oauth2/tokens"
Revoke Access
Revoke all tokens for an application:
curl -X DELETE \
-H "Authorization: Bearer $CODER_SESSION_TOKEN" \
"$CODER_URL/oauth2/tokens?client_id=$CLIENT_ID"
This ends existing sessions but leaves the application registered, so it can authorize again.
Delete an Application
Deleting an application is a separate operation from revoking its tokens. It removes the registration itself, so the client cannot authorize again without being registered anew.
In the web UI, navigate to Deployment Settings > OAuth2 Applications, select the application on the Applications tab, then select Delete. This requires permission to delete OAuth2 applications.
Or with the management API:
curl -X DELETE \
-H "Authorization: Bearer $CODER_SESSION_TOKEN" \
"$CODER_URL/api/v2/oauth2-provider/apps/$APP_ID"
This is also how you remove clients that registered themselves while dynamic client registration was enabled. Turning the setting off stops new registrations; it does not remove the ones already there.
Testing and Development
Coder provides comprehensive test scripts for OAuth2 development:
# Navigate to the OAuth2 test scripts
cd scripts/oauth2/
# Run the full automated test suite
./test-mcp-oauth2.sh
# Create a test application for manual testing
eval $(./setup-test-app.sh)
# Run an interactive browser-based test
./test-manual-flow.sh
# Clean up when done
./cleanup-test-app.sh
For more details on testing, see the OAuth2 test scripts README.
Common Issues
"OAuth2 experiment not enabled"
Add oauth2 to your experiment flags: coder server --experiments oauth2
"Invalid redirect_uri"
Ensure the redirect URI in your request exactly matches the one registered for your application.
"Invalid Callback URL" on the consent page
If you see this error when authorizing, the registered callback URL uses a
blocked scheme (javascript:, data:, file:, or ftp:). Update the
application's callback URL to a valid scheme (see
Callback URL schemes).
"PKCE verification failed"
Verify that the code_verifier used in the token request matches the one used to generate the code_challenge.
"public clients may not use the mailto/tel/sms scheme"
This error appears during client registration when a public client
(token_endpoint_auth_method: none) registers a redirect URI using the
mailto:, tel:, or sms: scheme. These schemes hand off to a mail
client, dialer, or SMS app instead of returning control to the
application that started the flow, so a public client registered with
one of them could never complete authorization. Register a redirect URI
the client can actually receive control on instead, such as a custom
scheme (myapp://callback) or a loopback HTTP address.
Callback URL schemes
Custom URI schemes (myapp://, vscode://, jetbrains://, etc.) are fully supported for native and desktop applications. The OS routes the redirect back to the registered application without requiring a running HTTP server.
The out-of-band URN urn:ietf:wg:oauth:2.0:oob is accepted from either client type, for clients that display the authorization code for the user to copy rather than receiving it on a redirect. No other URN is accepted.
The following schemes are blocked for security reasons: javascript:, data:, file:, ftp:.
Public clients (token_endpoint_auth_method: none) additionally cannot register mailto:, tel:, or sms: redirect URIs, since those schemes hand off to another app rather than returning an authorization code to the client. Confidential clients are not subject to this restriction.
Security Considerations
- Use HTTPS: Always use HTTPS in production to protect tokens in transit
- Implement PKCE: PKCE is mandatory for all authorization code clients (public and confidential)
- Validate redirect URLs: Only register trusted redirect URIs. Dangerous
schemes (
javascript:,data:,file:,ftp:) are blocked by the server, custom URI schemes for native apps (myapp://) are permitted, and public clients additionally cannot usemailto:,tel:, orsms: - Rotate secrets: Periodically rotate client secrets using the management API
Limitations
As an experimental feature, the current implementation has limitations:
- No scope system - all tokens have full API access
- No client credentials grant support
- Implicit grant (
response_type=token) is not supported; OAuth 2.1 deprecated this flow due to token leakage risks, and requests returnunsupported_response_type - Limited to opaque access tokens (no JWT support)
Standards Compliance
This implementation follows established OAuth2 standards including RFC 6749 (OAuth2 core), RFC 7636 (PKCE), and the OAuth 2.1 draft. Coder enforces OAuth 2.1 requirements including mandatory PKCE for all authorization code grants, exact redirect URI string matching, rejection of the implicit grant, and CSRF protections on consent pages.
Next Steps
Feedback
This is an experimental feature under active development. Please report issues and feedback through GitHub Issues with the oauth2 label.

