Magic Links
Magic links provide passwordless authentication. Instead of entering a password, the user receives a one-time link by email. Following the link (or submitting its token to the API) authenticates the user and returns a standard JWT token pair.
Magic links are useful for:
- Reducing friction at sign-in — no password to remember
- Verifying email ownership in a single step
- Handling users who have forgotten their password
How It Works
- Your application calls the request endpoint with the user's email.
- YorAuth sends the user an email containing a magic link.
- The user clicks the link. Your frontend extracts the
tokenquery parameter. - Your frontend calls the verify endpoint with the token.
- YorAuth returns a token pair — the user is authenticated.
A magic link is valid for 15 minutes and can only be used once. If the user's email address is not yet verified, using a magic link will verify it automatically.
Request a Magic Link
Send a magic link to a user's email address.
Endpoint: POST /api/v1/applications/{applicationId}/users/magic-link
This endpoint always returns a success response to prevent email enumeration — no indication is given whether the email address exists in your application.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The user's email address. |
redirect_url | string | No | URL to redirect the user to after successful verification. Must be an allowed URL for the application. |
curl -X POST https://api.yorauth.com/api/v1/applications/your-application-id/users/magic-link \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"redirect_url": "https://yourapp.com/dashboard"
}'
const response = await fetch(
'https://api.yorauth.com/api/v1/applications/your-application-id/users/magic-link',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'jane@example.com',
redirect_url: 'https://yourapp.com/dashboard'
})
}
);
const data = await response.json();
Response 200 OK
{
"data": {
"message": "If an account with that email exists, a magic link has been sent."
}
}
Error Responses
| HTTP Status | Error Code | Description |
|---|---|---|
| 429 | AUTH_MAGIC_LINK_RATE_LIMITED | More than 3 magic link requests in the last 5 minutes for this email. |
| 400 | VALIDATION_INVALID_FORMAT | The redirect_url is not a valid URL or is not on the application's allowed list. |
Verify a Magic Link
Exchange the magic link token for a JWT token pair.
Endpoint: POST /api/v1/applications/{applicationId}/users/magic-link/verify
When a user clicks the magic link in their email, the link points to your frontend. Your frontend should extract the token query parameter from the URL and POST it to this endpoint.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | The token from the magic link URL query parameter. |
curl -X POST https://api.yorauth.com/api/v1/applications/your-application-id/users/magic-link/verify \
-H "Content-Type: application/json" \
-d '{
"token": "abc123def456ghi789..."
}'
// Extract token from the current URL
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
const response = await fetch(
'https://api.yorauth.com/api/v1/applications/your-application-id/users/magic-link/verify',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token })
}
);
const data = await response.json();
Response 200 OK
{
"data": {
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "ref_a1b2c3d4e5f6...",
"token_type": "Bearer",
"expires_in": 900,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "jane@example.com",
"name": "Jane Doe",
"email_verified": true,
"created_at": "2026-02-25T10:00:00Z",
"metadata": null
},
"redirect_url": "https://yourapp.com/dashboard"
}
}
The redirect_url field contains the URL that was passed when the magic link was requested, or null if none was provided. Your frontend should redirect the user to this URL after storing the tokens.
Error Responses
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | AUTH_INVALID_MAGIC_LINK | The token is invalid, already used, or does not exist. |
| 410 | AUTH_MAGIC_LINK_EXPIRED | The token was valid but has expired (links expire after 15 minutes). |
Frontend Integration Pattern
Here is a complete example of handling magic link verification in a JavaScript frontend:
// On your magic link callback page (e.g. /magic-link-verify)
async function handleMagicLinkVerification() {
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
const applicationId = params.get('application_id');
if (!token || !applicationId) {
showError('Invalid magic link. Please request a new one.');
return;
}
try {
const response = await fetch(
`https://api.yorauth.com/api/v1/applications/${applicationId}/users/magic-link/verify`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token })
}
);
const data = await response.json();
if (!response.ok) {
if (data.error?.code === 'AUTH_MAGIC_LINK_EXPIRED') {
showError('This link has expired. Please request a new one.');
} else {
showError('This link is invalid. Please request a new one.');
}
return;
}
// Store tokens
storeAccessToken(data.data.access_token);
storeRefreshToken(data.data.refresh_token);
// Redirect to the original destination or a default page
const destination = data.data.redirect_url || '/dashboard';
window.location.href = destination;
} catch (err) {
showError('Something went wrong. Please try again.');
}
}
Security Notes
- Each magic link token is stored as a SHA-256 hash — the plaintext is never persisted.
- Requesting a new magic link for the same email invalidates any previously unused links.
- The link is single-use. Once verified, the token is marked as used and cannot be reused.
- Rate limiting (3 requests per 5 minutes per email) prevents abuse.
- The response to the request endpoint is identical whether the email exists or not, preventing user enumeration.