Files
coder/docs/admin/integrations/oauth2-provider.md
T
Faur Ioan-Aurel 83fd4cf5c2 fix: OAuth2 cancel button in the authorization page not working (#24058)
Go's html/template has a built-in security filter (urlFilter) that only
allows http, https, and mailto URL schemes. Any other scheme gets
replaced with #ZgotmplZ.

The OAuth2 app's callback URL uses custom URI scheme which the filter
considers unsafe. For example the Coder JetBrains plugin exposes a
callback URI with the scheme jetbrains:// - which was effectively
changed by the template engine into #ZgotmplZ. Of course this is not an
actual callback. When users clicked the cancel button nothing happened.

The fix was simple - we now wrap the apps registered callback URI into
htmltemplate.URL. Usually this needs some validation otherwise the
linter will complain about it. The callback URI used by the Cancel logic
is actually validated by our backend when the client app
programmatically registered via the dynamic OAuth2 registration
endpoints, so we refactored the validation around that code and re-used
some of it in the Cancel handling to make sure we don't allow URIs like
`javascript` and `data`, even though in theory these URIs were already
validated.

In addition, while testing this PR with
https://github.com/coder/coder-jetbrains-toolbox/pull/209 I discovered
that we are also not compliant with
https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1 which requires
the server to attach the local state if it was provided by the client in
the original request. Also it is optional but generally a good practice
to include `error_description` in the error responses. In fact we follow
this pattern for the other types of error responses. So this is not a
one off.

- resolves #20323
<img width="1485" height="771" alt="Cancel_page_with_invalid_uri"
src="https://github.com/user-attachments/assets/5539d234-9ce3-4dda-b421-d023fc9aa99e"
/>
<img width="486" height="746" alt="Coder Toolbox handling the Cancel
button"
src="https://github.com/user-attachments/assets/acab71a6-d29c-4fa9-80ba-3c0095bbdc8f"
/>

<!--

If you have used AI to produce some or all of this PR, please ensure you
have read our [AI Contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
before submitting.

-->
2026-04-10 12:49:22 +03:00

311 lines
10 KiB
Markdown

# OAuth2 Provider (Experimental)
> [!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 `oauth2` experiment 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:
```bash
coder server --experiments oauth2
```
Or set the environment variable:
```env
CODER_EXPERIMENTS=oauth2
```
## Creating OAuth2 Applications
### Method 1: Web UI
1. Navigate to **Deployment Settings****OAuth2 Applications**
2. Click **Create Application**
3. Fill in the application details:
- **Name**: Your application name
- **Callback URL**: `https://yourapp.example.com/callback` (web) or `myapp://callback` (native/desktop)
- **Icon**: Optional icon URL
### Method 2: Management API
Create an application using the Coder API:
```bash
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:
```bash
curl -X POST \
-H "Authorization: Bearer $CODER_SESSION_TOKEN" \
"$CODER_URL/api/v2/oauth2-provider/apps/$APP_ID/secrets"
```
## 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 is `client_id` and the password is `client_secret`.
- `client_secret_post`: Form-based authentication where `client_id` and `client_secret` are sent in the request body.
Coder supports both methods for compatibility; existing integrations using `client_secret_post` do not need to change.
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.
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
1. **Authorization Request**: Redirect users to Coder's authorization endpoint:
```url
https://coder.example.com/oauth2/authorize?
client_id=your-client-id&
response_type=code&
redirect_uri=https://yourapp.example.com/callback&
state=random-string
```
2. **Token Exchange**: Exchange the authorization code for an access token.
**Option A: HTTP Basic authentication (`client_secret_basic`, recommended)**
```bash
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`)**
```bash
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"
```
3. **API Access**: Use the access token to call Coder's API:
```bash
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](#pkce-flow-required) 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:
1. Generate a code verifier and challenge:
```bash
CODE_VERIFIER=$(openssl rand -base64 96 | tr -d "=+/" | cut -c1-128)
CODE_CHALLENGE=$(echo -n $CODE_VERIFIER | openssl dgst -sha256 -binary | base64 | tr -d "=+/" | cut -c1-43)
```
2. Include PKCE parameters in the authorization request:
```url
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
```
3. Include the code verifier in the token exchange (see [Client Authentication Methods](#client-authentication-methods)):
```bash
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](https://datatracker.ietf.org/doc/html/rfc8414) and [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728).
## Token Management
### Refresh Tokens
Refresh an expired access token.
**Option A: HTTP Basic authentication (`client_secret_basic`)**
```bash
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`)**
```bash
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:
```bash
curl -X DELETE \
-H "Authorization: Bearer $CODER_SESSION_TOKEN" \
"$CODER_URL/oauth2/tokens?client_id=$CLIENT_ID"
```
## Testing and Development
Coder provides comprehensive test scripts for OAuth2 development:
```bash
# 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](../../../scripts/oauth2/README.md).
## 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](#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`.
## 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 following schemes are blocked for security reasons: `javascript:`, `data:`, `file:`, `ftp:`.
## 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,
but custom URI schemes for native apps (`myapp://`) are permitted
- **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 return
`unsupported_response_type`
- Limited to opaque access tokens (no JWT support)
## Standards Compliance
This implementation follows established OAuth2 standards including
[RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) (OAuth2 core),
[RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) (PKCE), and the
[OAuth 2.1 draft](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12).
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
- Review the [API Reference](../../reference/api/index.md) for complete endpoint documentation
- Check [External Authentication](../external-auth/index.md) for configuring Coder as an OAuth2 client
- See [Security Best Practices](../security/index.md) for deployment security guidance
## Feedback
This is an experimental feature under active development. Please report issues and feedback through [GitHub Issues](https://github.com/coder/coder/issues) with the `oauth2` label.