mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(docs): token_endpoint_auth_method in OAuth server docs (#43128)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Docs update ## Summary The OAuth server supports three token endpoint authentication methods (`none`, `client_secret_basic`, `client_secret_post`), but the docs only showed `client_secret_post` implicitly without labeling it, and never mentioned client_secret_basic (the actual default for confidential clients per RFC 7591). - Add `token_endpoint_auth_method` explanation with defaults/constraints to the client registration section in getting-started.mdx - Update registration examples (JS, Python, cURL) and response JSON to include token_endpoint_auth_method - Restructure token exchange and refresh token sections in oauth-flows.mdx to show all three auth methods with clear labels - Add `client_secret_basic` examples using HTTP Basic auth header
This commit is contained in:
1 parent
85e6b1143f
commit
d810b7772b
2 files changed
+122
-8
No files matched your search
@@ -491,6 +491,22 @@ Store the client secret securely. It will only be shown once. If you lose it, yo
|
||||
|
||||
</Admonition>
|
||||
|
||||
#### Token endpoint authentication method
|
||||
|
||||
When a client exchanges an authorization code or refreshes a token, it must authenticate with the token endpoint. The `token_endpoint_auth_method` controls how this authentication happens:
|
||||
|
||||
| Method | Description | Used by |
|
||||
| --- | --- | --- |
|
||||
| `none` | No client authentication. Only `client_id` is sent in the request body. | Public clients (required) |
|
||||
| `client_secret_basic` | Client credentials sent via HTTP Basic auth (`Authorization: Basic <base64(client_id:client_secret)>`). **This is the default for confidential clients.** | Confidential clients |
|
||||
| `client_secret_post` | Client credentials sent in the request body (`client_id` and `client_secret` as form parameters). | Confidential clients |
|
||||
|
||||
**Defaults:** Public clients default to `none`. Confidential clients default to `client_secret_basic` (per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591#section-2)).
|
||||
|
||||
**Constraints:** Public clients must use `none`. Confidential clients cannot use `none`.
|
||||
|
||||
You can set this when registering a client via the dashboard or programmatically. See [OAuth Flows](/docs/guides/auth/oauth-server/oauth-flows#step-5-token-exchange) for examples of each method in action.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="programmatically" label="Programmatically">
|
||||
|
||||
@@ -518,6 +534,8 @@ const { data, error } = await supabase.auth.admin.oauth.createClient({
|
||||
name: 'My Third-Party App',
|
||||
redirect_uris: ['https://my-app.com/auth/callback', 'https://my-app.com/auth/silent-callback'],
|
||||
client_type: 'confidential',
|
||||
// Optional: defaults to 'client_secret_basic' for confidential, 'none' for public
|
||||
token_endpoint_auth_method: 'client_secret_basic',
|
||||
})
|
||||
|
||||
if (error) {
|
||||
@@ -548,7 +566,9 @@ response = supabase.auth.admin.oauth.create_client({
|
||||
'https://my-app.com/auth/callback',
|
||||
'https://my-app.com/auth/silent-callback'
|
||||
],
|
||||
'client_type': 'confidential'
|
||||
'client_type': 'confidential',
|
||||
# Optional: defaults to 'client_secret_basic' for confidential, 'none' for public
|
||||
'token_endpoint_auth_method': 'client_secret_basic'
|
||||
})
|
||||
|
||||
print('Client created:', response)
|
||||
@@ -572,7 +592,8 @@ curl -X POST 'https://<project-ref>.supabase.co/auth/v1/admin/oauth/clients' \
|
||||
"https://my-app.com/auth/callback",
|
||||
"https://my-app.com/auth/silent-callback"
|
||||
],
|
||||
"client_type": "confidential"
|
||||
"client_type": "confidential",
|
||||
"token_endpoint_auth_method": "client_secret_basic"
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -585,7 +606,8 @@ curl -X POST 'http://localhost:54321/auth/v1/admin/oauth/clients' \
|
||||
-d '{
|
||||
"name": "Local Dev App",
|
||||
"redirect_uris": ["http://localhost:3000/auth/callback"],
|
||||
"client_type": "confidential"
|
||||
"client_type": "confidential",
|
||||
"token_endpoint_auth_method": "client_secret_post"
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -598,6 +620,7 @@ Response:
|
||||
"name": "My Third-Party App",
|
||||
"redirect_uris": ["https://my-app.com/auth/callback", "https://my-app.com/auth/silent-callback"],
|
||||
"client_type": "confidential",
|
||||
"token_endpoint_auth_method": "client_secret_basic",
|
||||
"created_at": "2025-01-15T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -230,7 +230,11 @@ The error parameters allow clients to display relevant error messages to users:
|
||||
|
||||
### Step 5: Token exchange
|
||||
|
||||
The client exchanges the authorization code for tokens by making a POST request to the token endpoint:
|
||||
The client exchanges the authorization code for tokens by making a POST request to the token endpoint. How the client authenticates depends on its `token_endpoint_auth_method` (set during [client registration](/docs/guides/auth/oauth-server/getting-started#token-endpoint-authentication-method)).
|
||||
|
||||
#### Public clients (`token_endpoint_auth_method: none`)
|
||||
|
||||
Public clients send only the `client_id` in the request body with no secret:
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
@@ -242,7 +246,29 @@ curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
-d 'code_verifier=<code-verifier>'
|
||||
```
|
||||
|
||||
For confidential clients (with client secret):
|
||||
#### Confidential clients (`token_endpoint_auth_method: client_secret_basic`)
|
||||
|
||||
This is the **default** for confidential clients. Credentials are sent via the `Authorization` header using HTTP Basic authentication (base64-encoded `client_id:client_secret`):
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-u '<client-id>:<client-secret>' \
|
||||
-d 'grant_type=authorization_code' \
|
||||
-d 'code=<authorization-code>' \
|
||||
-d 'redirect_uri=<redirect-uri>' \
|
||||
-d 'code_verifier=<code-verifier>'
|
||||
```
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
The `-u` flag in cURL automatically encodes the credentials and sets the `Authorization: Basic <base64(client_id:client_secret)>` header. If you're not using cURL, you must base64-encode the `client_id:client_secret` string yourself.
|
||||
|
||||
</Admonition>
|
||||
|
||||
#### Confidential clients (`token_endpoint_auth_method: client_secret_post`)
|
||||
|
||||
Credentials are sent as form parameters in the request body:
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
@@ -261,7 +287,7 @@ curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
// Retrieve the code verifier from storage
|
||||
const codeVerifier = sessionStorage.getItem('code_verifier')
|
||||
|
||||
// Exchange code for tokens
|
||||
// --- Public clients (token_endpoint_auth_method: none) ---
|
||||
const response = await fetch(`https://<project-ref>.supabase.co/auth/v1/oauth/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -276,6 +302,37 @@ const response = await fetch(`https://<project-ref>.supabase.co/auth/v1/oauth/to
|
||||
}),
|
||||
})
|
||||
|
||||
// --- Confidential clients (token_endpoint_auth_method: client_secret_basic) ---
|
||||
const response = await fetch(`https://<project-ref>.supabase.co/auth/v1/oauth/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Authorization: 'Basic ' + btoa('<client-id>:<client-secret>'),
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: authorizationCode,
|
||||
redirect_uri: '<redirect-uri>',
|
||||
code_verifier: codeVerifier,
|
||||
}),
|
||||
})
|
||||
|
||||
// --- Confidential clients (token_endpoint_auth_method: client_secret_post) ---
|
||||
const response = await fetch(`https://<project-ref>.supabase.co/auth/v1/oauth/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: authorizationCode,
|
||||
client_id: '<client-id>',
|
||||
client_secret: '<client-secret>',
|
||||
redirect_uri: '<redirect-uri>',
|
||||
code_verifier: codeVerifier,
|
||||
}),
|
||||
})
|
||||
|
||||
const tokens = await response.json()
|
||||
```
|
||||
|
||||
@@ -377,7 +434,9 @@ Clients should refresh access tokens when:
|
||||
|
||||
### Refresh request
|
||||
|
||||
Make a POST request to the token endpoint with the refresh token:
|
||||
Make a POST request to the token endpoint with the refresh token. The client authenticates the same way as during the [token exchange](#step-5-token-exchange), based on its `token_endpoint_auth_method`.
|
||||
|
||||
#### Public clients (`token_endpoint_auth_method: none`)
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
@@ -387,7 +446,17 @@ curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
-d 'client_id=<client-id>'
|
||||
```
|
||||
|
||||
For confidential clients:
|
||||
#### Confidential clients (`token_endpoint_auth_method: client_secret_basic`)
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-u '<client-id>:<client-secret>' \
|
||||
-d 'grant_type=refresh_token' \
|
||||
-d 'refresh_token=<refresh-token>'
|
||||
```
|
||||
|
||||
#### Confidential clients (`token_endpoint_auth_method: client_secret_post`)
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
@@ -401,6 +470,7 @@ curl -X POST 'https://<project-ref>.supabase.co/auth/v1/oauth/token' \
|
||||
#### Example in JavaScript
|
||||
|
||||
```javascript
|
||||
// Public clients (token_endpoint_auth_method: none)
|
||||
async function refreshAccessToken(refreshToken) {
|
||||
const response = await fetch(`https://<project-ref>.supabase.co/auth/v1/oauth/token`, {
|
||||
method: 'POST',
|
||||
@@ -420,6 +490,27 @@ async function refreshAccessToken(refreshToken) {
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
// Confidential clients (token_endpoint_auth_method: client_secret_basic)
|
||||
async function refreshAccessTokenConfidential(refreshToken) {
|
||||
const response = await fetch(`https://<project-ref>.supabase.co/auth/v1/oauth/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Authorization: 'Basic ' + btoa('<client-id>:<client-secret>'),
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to refresh token')
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
```
|
||||
|
||||
### Refresh response
|
||||
|
||||
Reference in new issue
Block a user