mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
docs(auth-hooks): clarify webhook status codes and document email_change quirk (#38141)
* docs(auth-hooks): clarify webhook status codes and document email_change quirk
- Clarify hook response handling:
- Note that some hooks do not support 204 responses if they require a body
- Update retry-able error behavior for 429/503 with explicit 5s budget
- Add note that Retry-After header is only checked for non-empty value, not parsed
- Expand send-email-hook docs:
- Add detailed section on `email_change` behavior
- Explain secure vs non-secure modes and when two emails must be sent
- Document long-standing quirk where `token_hash` and `token_hash_new`
are swapped relative to expected naming
- Extend enum to include `reauthentication`
- Improve confirmation URL example using URLSearchParams and projectRef
* fix: fix linter error
* fix: pr feedback
* fix: pnpm format
* fix: pr feedback
---------
Co-authored-by: Chris Stockton <chris.stockton@supabase.io>
This commit is contained in:
1 parent
db4ae7e326
commit
97a447ac5c
2 files changed
+70
-14
No files matched your search
@@ -313,23 +313,37 @@ Hooks return status codes based on the nature of the response. These status code
|
||||
| HTTP Status Code | Description | Example Usage |
|
||||
| ---------------- | ------------------------------------------------------------- | ---------------------------------------------- |
|
||||
| 200, 202, 204 | Valid response, proceed | Successful processing of the request |
|
||||
| 429, 503 | Retry-able errors with Retry-after header supported | Temporary server overload or maintenance |
|
||||
| 403, 400 | Treated as Internal Server Errors and return a 500 Error Code | Malformed requests or insufficient permissions |
|
||||
| 429, 503 | Retry-able errors | Temporary server overload or maintenance |
|
||||
|
||||
Errors are responses which contain status codes 400 and above. On a retry-able error, such as an error with a `429` or `503` status code, HTTP Hooks will attempt up to three retries with a back-off of two seconds.
|
||||
<Admonition type="note">
|
||||
|
||||
`204` Status is not supported by the following hooks which require a response body:
|
||||
|
||||
- [Custom Access Token](/docs/guides/auth/auth-hooks/custom-access-token-hook)
|
||||
- [MFA Verification Attempt](/docs/guides/auth/auth-hooks/mfa-verification-hook)
|
||||
- [Password Verification Attempt](/docs/guides/auth/auth-hooks/password-verification-hook)
|
||||
|
||||
</Admonition>
|
||||
|
||||
Errors are responses which contain status codes 400 and above. On a retry-able error, such as an error with a `429` or `503` status code, HTTP Hooks will attempt up to three retries with a back-off of two seconds. We have a time budget of 5s for the entire webhook invocation, including retry requests.
|
||||
|
||||
Here's a sample HTTP retry schedule:
|
||||
|
||||
| Time Since Start (HH:MM:SS) | Event | Notes |
|
||||
| --------------------------- | ----------------------- | ------------------------------------------------- |
|
||||
| 00:00:00 | Initial Attempt | Initial invocation begins. |
|
||||
| 00:00:05 | Initial Attempt Timeout | Initial invocation must complete. |
|
||||
| 00:00:07 | Retry Start #1 | After 2 sec delay, first retry begins. |
|
||||
| 00:00:12 | Retry Timeout #1 | First retry timeout. |
|
||||
| 00:00:14 | Retry Start #2 | After 2 sec delay, second retry begins. |
|
||||
| 00:00:19 | Retry Timeout #2 | Second retry timeout. Returns an error on failure |
|
||||
| Time Since Start (HH:MM:SS) | Event | Notes |
|
||||
| --------------------------- | --------------------- | -------------------------------------------------------------------------------- |
|
||||
| 00:00:00 | Initial Attempt | Initial invocation begins. |
|
||||
| 00:00:02 | Initial Attempt Fails | Initial invocation returns `429` or `503` with non-empty `retry-after` header. |
|
||||
| 00:00:04 | Retry Start #1 | After 2 sec delay, first retry begins. |
|
||||
| 00:00:05 | Retry Timeout #1 | First retry times out, exceeded 5 second budget and invocation returns an error. |
|
||||
|
||||
Return a retry-able error by attaching a appropriate status code (`429`, `503` ) and a non-empty `retry-after` header
|
||||
Return a retry-able error by attaching a appropriate status code (`429`, `503`) and a non-empty `retry-after` header
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
`Retry-After` Supabase Auth does not fully support the `Retry-After` header as described in RFC7231, we only check if it is a non-empty value such as `true` or `10`. Setting this to your preferred value is fine as a future update may address this.
|
||||
|
||||
</Admonition>
|
||||
|
||||
```jsx
|
||||
return new Response(
|
||||
|
||||
@@ -17,6 +17,33 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
|
||||
| Disabled | Enabled | Email Signups Disabled |
|
||||
| Disabled | Disabled | Email Signups Disabled |
|
||||
|
||||
## Email change behavior and token hash mapping
|
||||
|
||||
When `email_action_type` is `email_change`, the hook payload can include one or two OTPs and their hashes. This depends on your [Secure Email Change](/dashboard/project/_/auth/providers?provider=Email) setting.
|
||||
|
||||
- Secure Email Change enabled: two OTPs are generated, one for the current email (`user.email`) and one for the new email (`user.email_new`). You must send two emails.
|
||||
- Secure Email Change disabled: only one OTP is generated for the new email. You send a single email.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Important quirk (backward compatibility):
|
||||
|
||||
- `email_data.token_hash_new` = Hash(`user.email`, `email_data.token`)
|
||||
- `email_data.token_hash` = Hash(`user.email_new`, `email_data.token_new`)
|
||||
|
||||
This naming is historical and kept for backward compatibility. Do not assume that the `_new` suffix refers to the new email.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### What to send
|
||||
|
||||
If both `token_hash` and `token_hash_new` are present, send two messages:
|
||||
|
||||
- To the current email (`user.email`): use `token` with `token_hash_new`.
|
||||
- To the new email (`user.email_new`): use `token_new` with `token_hash`.
|
||||
|
||||
If only one token/hash pair is present, send a single email. In non-secure mode, this is typically the new email OTP. Use `token` with `token_hash` or `token_new` with `token_hash`, depending on which fields are present in the payload.
|
||||
|
||||
**Inputs**
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -282,7 +309,15 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
|
||||
},
|
||||
"email_action_type": {
|
||||
"type": "string",
|
||||
"enum": ["signup", "invite", "magiclink", "recovery", "email_change", "email"]
|
||||
"enum": [
|
||||
"signup",
|
||||
"invite",
|
||||
"magiclink",
|
||||
"recovery",
|
||||
"email_change",
|
||||
"email",
|
||||
"reauthentication"
|
||||
]
|
||||
},
|
||||
"site_url": {
|
||||
"type": "string",
|
||||
@@ -578,6 +613,7 @@ import { readAll } from 'https://deno.land/std/io/read_all.ts'
|
||||
const postmarkEndpoint = 'https://api.postmarkapp.com/email'
|
||||
// Replace this with your email
|
||||
const FROM_EMAIL = 'myemail@gmail.com'
|
||||
const PROJECT_REF = '<your-project-ref>'
|
||||
|
||||
// Email Subjects
|
||||
const subjects = {
|
||||
@@ -642,8 +678,14 @@ const templates = {
|
||||
}
|
||||
|
||||
function generateConfirmationURL(email_data) {
|
||||
// TODO: replace the ref with your project ref
|
||||
return `https://<ref>.supabase.co/auth/v1/verify?token=${email_data.token_hash}&type=${email_data.email_action_type}&redirect_to=${email_data.redirect_to}`
|
||||
const baseUrl = `https://${PROJECT_REF}.supabase.co/auth/v1/verify`
|
||||
const params = new URLSearchParams({
|
||||
token: email_data.token_hash,
|
||||
type: email_data.email_action_type,
|
||||
redirect_to: email_data.redirect_to,
|
||||
})
|
||||
|
||||
return `${baseUrl}?${params.toString()}`
|
||||
}
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
|
||||
Reference in new issue
Block a user