curl -X POST "http://localhost:8080/magiclink" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"create_user": true
}'
const response = await fetch('http://localhost:8080/magiclink', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'user@example.com',
create_user: true
}),
});
const result = await response.json();
import requests
data = {
"email": "user@example.com",
"create_user": True
}
response = requests.post('http://localhost:8080/magiclink', json=data)
result = response.json()
package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
data := map[string]interface{}{
"email": "user@example.com",
"create_user": true,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:8080/magiclink",
"application/json",
bytes.NewBuffer(jsonData),
)
// Handle response...
}
{
"message_id": "msg_1234567890abcdef"
}
Authentication
Magic Link
Send magic link for passwordless authentication
POST
/
magiclink
curl -X POST "http://localhost:8080/magiclink" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"create_user": true
}'
const response = await fetch('http://localhost:8080/magiclink', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'user@example.com',
create_user: true
}),
});
const result = await response.json();
import requests
data = {
"email": "user@example.com",
"create_user": True
}
response = requests.post('http://localhost:8080/magiclink', json=data)
result = response.json()
package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
data := map[string]interface{}{
"email": "user@example.com",
"create_user": true,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:8080/magiclink",
"application/json",
bytes.NewBuffer(jsonData),
)
// Handle response...
}
{
"message_id": "msg_1234567890abcdef"
}
Send a magic link via email for passwordless authentication. Users can sign in by clicking the link in their email without entering a password.
This endpoint does not require authentication and can create new users if
create_user is set to true.curl -X POST "http://localhost:8080/magiclink" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"create_user": true
}'
const response = await fetch('http://localhost:8080/magiclink', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'user@example.com',
create_user: true
}),
});
const result = await response.json();
import requests
data = {
"email": "user@example.com",
"create_user": True
}
response = requests.post('http://localhost:8080/magiclink', json=data)
result = response.json()
package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
data := map[string]interface{}{
"email": "user@example.com",
"create_user": true,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:8080/magiclink",
"application/json",
bytes.NewBuffer(jsonData),
)
// Handle response...
}
Request Body
string
required
Email address to send the magic link to.
boolean
Whether to create a new user if the email doesn’t exist. Defaults to false.
string
URL to redirect to after successful authentication. If not provided, uses the default redirect URL.
object
Additional user metadata to store if creating a new user.
string
Captcha token for verification if captcha is enabled.
Response
string
Unique identifier for the sent magic link email (when available)
{
"message_id": "msg_1234567890abcdef"
}
Error Responses
{
"code": 400,
"msg": "Invalid email format",
"details": "Please provide a valid email address"
}
{
"code": 404,
"msg": "User not found",
"details": "No user found with this email address. Set create_user to true to create a new account."
}
{
"code": 429,
"msg": "Too many requests",
"details": "Please wait before requesting another magic link"
}
Magic Link Flow
1
Request Magic Link
User enters their email address and requests a magic link
2
Email Sent
A magic link email is sent to the user’s email address
3
User Clicks Link
User clicks the magic link in their email
4
Authentication
User is automatically authenticated and redirected to your application
Magic Link Email Template
<!DOCTYPE html>
<html>
<head>
<title>Sign in to Strike</title>
</head>
<body>
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<h1>Sign in to Strike</h1>
<p>Hi there,</p>
<p>Click the button below to sign in to your Strike account:</p>
<a href="{{.MagicLinkURL}}"
style="background-color: #0D9373; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
Sign In
</a>
<p>Or copy and paste this link into your browser:</p>
<p>{{.MagicLinkURL}}</p>
<p>This link will expire in 1 hour for security reasons.</p>
<p>If you didn't request this sign-in link, you can safely ignore this email.</p>
<p>Best regards,<br>The Strike Team</p>
</div>
</body>
</html>
Implementation Examples
React Magic Link Form
import { useState } from 'react';
function MagicLinkForm() {
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const [sent, setSent] = useState(false);
const [error, setError] = useState('');
const [createUser, setCreateUser] = useState(true);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
try {
const response = await fetch('/api/auth/magiclink', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
create_user: createUser,
redirect_to: window.location.origin + '/dashboard'
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.msg || 'Failed to send magic link');
}
setSent(true);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (sent) {
return (
<div className="magic-link-success">
<h2>Check your email!</h2>
<p>
We've sent a magic link to <strong>{email}</strong>
</p>
<p>
Click the link in your email to sign in instantly.
</p>
<p>
<small>
Didn't receive the email? Check your spam folder or{' '}
<button
onClick={() => setSent(false)}
className="link-button"
>
try again
</button>
</small>
</p>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="magic-link-form">
<h2>Sign in with Magic Link</h2>
<p>
Enter your email address and we'll send you a link to sign in instantly.
</p>
{error && (
<div className="error-message">
{error}
</div>
)}
<div className="form-group">
<label htmlFor="email">Email Address</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
required
/>
</div>
<div className="form-group">
<label className="checkbox-label">
<input
type="checkbox"
checked={createUser}
onChange={(e) => setCreateUser(e.target.checked)}
/>
Create account if it doesn't exist
</label>
</div>
<button type="submit" disabled={loading || !email}>
{loading ? 'Sending...' : 'Send Magic Link'}
</button>
<p>
Prefer a password? <a href="/login">Sign in with password</a>
</p>
</form>
);
}
export default MagicLinkForm;
Magic Link with User Creation
import { useState } from 'react';
function MagicLinkSignup() {
const [formData, setFormData] = useState({
email: '',
firstName: '',
lastName: ''
});
const [loading, setLoading] = useState(false);
const [sent, setSent] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
try {
const response = await fetch('/api/auth/magiclink', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: formData.email,
create_user: true,
data: {
first_name: formData.firstName,
last_name: formData.lastName
},
redirect_to: window.location.origin + '/welcome'
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.msg || 'Failed to send magic link');
}
setSent(true);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleInputChange = (field, value) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
if (sent) {
return (
<div className="signup-success">
<h2>Welcome to Strike!</h2>
<p>
We've sent a magic link to <strong>{formData.email}</strong>
</p>
<p>
Click the link to complete your account setup and sign in.
</p>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="magic-signup-form">
<h2>Join Strike</h2>
<p>
Create your account and sign in instantly with a magic link.
</p>
{error && (
<div className="error-message">
{error}
</div>
)}
<div className="form-row">
<div className="form-group">
<label htmlFor="firstName">First Name</label>
<input
type="text"
id="firstName"
value={formData.firstName}
onChange={(e) => handleInputChange('firstName', e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="lastName">Last Name</label>
<input
type="text"
id="lastName"
value={formData.lastName}
onChange={(e) => handleInputChange('lastName', e.target.value)}
required
/>
</div>
</div>
<div className="form-group">
<label htmlFor="email">Email Address</label>
<input
type="email"
id="email"
value={formData.email}
onChange={(e) => handleInputChange('email', e.target.value)}
placeholder="Enter your email"
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Creating Account...' : 'Create Account & Send Magic Link'}
</button>
<p>
Already have an account? <a href="/login">Sign in</a>
</p>
</form>
);
}
export default MagicLinkSignup;
Node.js Backend Handler
const express = require('express');
const { body, validationResult } = require('express-validator');
const router = express.Router();
router.post('/magiclink', [
body('email').isEmail().normalizeEmail(),
body('create_user').optional().isBoolean(),
body('data').optional().isObject()
], async (req, res) => {
try {
// Check validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
code: 400,
msg: 'Invalid request data',
details: errors.array()
});
}
const { email, create_user, redirect_to, data, captcha_token } = req.body;
// Call Strike Auth Service
const response = await fetch(`${process.env.AUTH_SERVICE_URL}/magiclink`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
create_user: create_user || false,
redirect_to: redirect_to || `${process.env.APP_URL}/dashboard`,
data,
captcha_token
}),
});
const result = await response.json();
if (!response.ok) {
return res.status(response.status).json(result);
}
// Log magic link request
console.log(`Magic link sent to: ${email}, create_user: ${create_user}`);
res.json(result);
} catch (error) {
console.error('Magic link error:', error);
res.status(500).json({
code: 500,
msg: 'Internal server error',
details: 'Please try again later'
});
}
});
module.exports = router;
Use Cases
Passwordless Login
Perfect for users who prefer not to remember passwords:{
"email": "user@example.com",
"create_user": false,
"redirect_to": "https://yourapp.com/dashboard"
}
Quick Signup
Streamlined user registration without password requirements:{
"email": "newuser@example.com",
"create_user": true,
"data": {
"first_name": "John",
"last_name": "Doe",
"marketing_consent": true
},
"redirect_to": "https://yourapp.com/welcome"
}
Account Recovery
Alternative to password reset for users who prefer magic links:{
"email": "user@example.com",
"create_user": false,
"redirect_to": "https://yourapp.com/account-recovered"
}
Security Features
- Token Expiration: Magic links expire after 1 hour
- Single Use: Links can only be used once
- Rate Limiting: Prevents spam and abuse
- Secure Tokens: Cryptographically secure random tokens
- Email Verification: Inherent email verification through link click
Rate Limiting
This endpoint is rate limited to prevent abuse:| Limit Type | Limit | Window |
|---|---|---|
| Per Email | 3 requests | 5 minutes |
| Per IP | 10 requests | 10 minutes |
Best Practices
User Experience
User Experience
- Provide clear instructions about checking email
- Include troubleshooting tips for email delivery issues
- Offer alternative sign-in methods
- Show loading states during email sending
- Implement resend functionality with cooldown
Security
Security
- Use secure, random tokens with sufficient entropy
- Implement proper token expiration (1 hour recommended)
- Log magic link requests for security monitoring
- Validate email addresses before sending
- Implement rate limiting to prevent abuse
Email Delivery
Email Delivery
- Use reputable email service providers
- Implement proper email authentication (SPF, DKIM, DMARC)
- Monitor delivery rates and bounce rates
- Provide clear sender identification
- Include plain text version of emails
Testing
Unit Tests
describe('POST /magiclink', () => {
test('should send magic link for existing user', async () => {
const response = await request(app)
.post('/magiclink')
.send({
email: 'existing@example.com',
create_user: false
})
.expect(200);
expect(response.body.message_id).toBeDefined();
});
test('should create user and send magic link', async () => {
const response = await request(app)
.post('/magiclink')
.send({
email: 'new@example.com',
create_user: true,
data: {
first_name: 'Test',
last_name: 'User'
}
})
.expect(200);
expect(response.body.message_id).toBeDefined();
});
test('should reject invalid email', async () => {
await request(app)
.post('/magiclink')
.send({
email: 'invalid-email',
create_user: true
})
.expect(400);
});
test('should return 404 for non-existent user when create_user is false', async () => {
await request(app)
.post('/magiclink')
.send({
email: 'nonexistent@example.com',
create_user: false
})
.expect(404);
});
});
Related Endpoints
Verify Magic Link
Complete magic link authentication
Resend Magic Link
Resend magic link email
User Signup
Traditional email/password registration
User Login
Password-based authentication