Skip to main content

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

  1. Your application calls the request endpoint with the user's email.
  2. YorAuth sends the user an email containing a magic link.
  3. The user clicks the link. Your frontend extracts the token query parameter.
  4. Your frontend calls the verify endpoint with the token.
  5. 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.


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

FieldTypeRequiredDescription
emailstringYesThe user's email address.
redirect_urlstringNoURL to redirect the user to after successful verification. Must be an allowed URL for the application.
bash
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"
  }'
javascript
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

json
{
  "data": {
    "message": "If an account with that email exists, a magic link has been sent."
  }
}

Error Responses

HTTP StatusError CodeDescription
429AUTH_MAGIC_LINK_RATE_LIMITEDMore than 3 magic link requests in the last 5 minutes for this email.
400VALIDATION_INVALID_FORMATThe redirect_url is not a valid URL or is not on the application's allowed list.

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

FieldTypeRequiredDescription
tokenstringYesThe token from the magic link URL query parameter.
bash
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..."
  }'
javascript
// 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

json
{
  "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 StatusError CodeDescription
400AUTH_INVALID_MAGIC_LINKThe token is invalid, already used, or does not exist.
410AUTH_MAGIC_LINK_EXPIREDThe 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:

javascript
// 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.