A Directus extension that adds secure, passwordless authentication to your Directus instance through magic links sent via email.
I recommend that you first use the built-in demo at /magic-link-ui before you integrate this into your custom Directus front-end. that way you know that all the requirements/settings are correct (which could save you from a lot of time).
After full integration, you can selective disable the magic-link-ui endpoint and only leave magic-link-api active.
- Passwordless Authentication: Allow users to log in with just their email address
- Secure Implementation:
- Cryptographically secure time tokens (1 minute window of use)
- Protection against timing attacks
- Prevention of user enumeration
- Token invalidation when new tokens are requested
- Rate Limiting: Configurable limits on magic link requests per hour
- Role-Based Access Control: Restrict magic link usage to specific user roles
- Detailed Logging: Comprehensive logs for debugging and security auditing
- IP & User Agent Tracking: All requests are logged with IP address and user agent for security auditing
- SMTP Configuration: Uses Directus's built-in email configuration (environment variables)
- Customizable: Configure expiration times, email subjects, and more
This is currently not implemented, but could be added in future versions if needed:
- Make the full e-mail customizable (not just the subject)
- Make the links true single use (there is currently a 1 minute window after the first use to use it again, to allow for accidental triggering by e-mail clients)
- Sending email using any
EMAIL_TRANSPORTmode (not only SMTP) - Installation via the Directus Marketplace
- Very complex: Login module that works with the Directus Data Studio app (see Data Studio Integration Limitations below)
- Directus 10.0.0 or higher
- PostgreSQL, MySQL, or SQLite database
- SMTP server for sending emails (configured with Directus environment variables)
-
Install the extension
Currently only manual installation is supported.
- Create a folder to your Directus extensions directory:
./extensions/directus-magic-link-auth - In this repository, copy the
/distfolder and it's content into your newly createddirectus-magic-link-authfolder - In this repository, also copy the
package.jsoninto your newly createddirectus-magic-link-authfolder
You should now have the following structure:
/extensions /directus-magic-link-auth /dist /api.js /app.js package.json - Create a folder to your Directus extensions directory:
-
Create the database table
Run the following SQL code to create the required table
extension_magic_link:-- Extension table for magic links CREATE TABLE extension_magic_link ( id serial PRIMARY KEY, email varchar(255) NOT NULL, token varchar(255) NOT NULL, expires_at timestamp NOT NULL, ip_address varchar(255) NOT NULL, user_agent text, used boolean NOT NULL DEFAULT false, created_at timestamp NOT NULL, email_sent boolean DEFAULT NULL, email_error varchar(255) ); -- Create indexes for better performance CREATE INDEX idx_magic_link_token ON extension_magic_link(token); CREATE INDEX idx_magic_link_email ON extension_magic_link(email); CREATE INDEX idx_magic_link_created_at ON extension_magic_link(created_at);
-
Configure environment variables
Make sure you have SMTP configuration set (see the section Configuration Options for all configuration options):
# SMTP Configuration (mirrors Directus SMTP email settings) EMAIL_SMTP_HOST=smtp.example.com EMAIL_SMTP_PORT=587 EMAIL_SMTP_USER=your-smtp-username EMAIL_SMTP_PASSWORD=your-smtp-password EMAIL_FROM="Your Name <email@example.com>" # URL Configuration PUBLIC_URL=https://your-directus-url.com # Optional: Internal URL for server-to-server calls (when behind proxy/NGINX) DIRECTUS_INTERNAL_URL=http://directus:8055
Important: This extension cannot be directly integrated with the Directus Data Studio interface due to technical limitations:
- Content Security Policy (CSP): Directus has strict CSP policies that prevent execution of inline JavaScript and external scripts within the Data Studio interface.
- Session Context: The Data Studio operates in a different session context than the public API endpoints, making direct authentication integration complex.
Instead, this extension provides:
- A working demo interface at
/magic-link-uithat shows the complete flow - Clean API endpoints (
/magic-link-api) that can be integrated with any custom frontend - Example code in the demo that developers can adapt for their own implementations
The extension includes a complete demo at /magic-link-ui that demonstrates the magic link flow without requiring JavaScript (to avoid CSP issues). This demo serves as:
- A working example of the complete authentication flow
- Reference implementation for developers building custom frontends
- Testing interface for the magic link functionality
Access the demo at: https://your-directus-url.com/magic-link-ui
Send a POST request to /magic-link-api/generate with the following body:
{
"email": "user@example.com"
}This will:
- Check if the user exists in your Directus instance
- Verify the user's role is allowed to use magic links
- Verify rate limits haven't been exceeded
- Generate a secure token
- Store the token in the database
- Send an email with a login link
When a user clicks the link in the email, they will be directed to:
https://your-directus-url.com/magic-link/verify?token=YOUR_TOKEN
This will:
- Validate the token
- Check if the token has expired or been used
- Verify the user's role is still allowed to use magic links
- If valid, authenticate the user and return a Directus session
- Invalidate the token to prevent reuse
The demo interface at /magic-link-ui provides complete example code that you can adapt for your own frontend. Here's a basic implementation:
<form id="magic-link-form">
<input type="email" id="email" placeholder="Enter your email" required />
<button type="submit">Send Magic Link</button>
</form>
<script>
document
.getElementById("magic-link-form")
.addEventListener("submit", async (e) => {
e.preventDefault();
const email = document.getElementById("email").value;
try {
const response = await fetch(
"https://your-directus-url.com/magic-link-api/generate",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
}
);
const data = await response.json();
if (data.success) {
alert("Check your email for a magic link!");
} else {
alert(data.message);
}
} catch (error) {
console.error("Error:", error);
alert("An error occurred. Please try again.");
}
});
</script>When users click the magic link, they'll be redirected to your verification page with a token parameter. Here's how to handle it:
// Extract token from URL parameters
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token');
if (token) {
// Verify the token with Directus
fetch(`https://your-directus-url.com/magic-link-api/verify?token=${token}`)
.then(response => response.json())
.then(data => {
if (data.success) {
// Token is valid, user is authenticated
// The response includes session data you can use
console.log('User authenticated:', data.data);
// Redirect to your application
window.location.href = '/dashboard';
} else {
// Handle invalid/expired token
alert('Invalid or expired magic link');
}
})
.catch(error => {
console.error('Verification error:', error);
});
}Note: See the demo interface source code in /src/magic-link-ui/index.ts for a complete reference implementation without JavaScript (useful for understanding the server-side flow).
| Environment Variable | Description | Default |
|---|---|---|
EMAIL_SMTP_HOST |
SMTP server hostname | smtp.example.com |
EMAIL_SMTP_PORT |
SMTP server port | 587 |
EMAIL_SMTP_SECURE |
Use secure connection (SSL/TLS) | false |
EMAIL_SMTP_USER |
SMTP username | - |
EMAIL_SMTP_PASSWORD |
SMTP password | - |
EMAIL_FROM |
From email address | EMAIL_SMTP_USER |
MAGIC_LINK_EXPIRATION_MINUTES |
How long the magic link is valid | 15 |
MAGIC_LINK_SUBJECT |
Email subject | "Your Magic Login Link" |
MAGIC_LINK_VERIFY_ENDPOINT |
Endpoint for verification | "/magic-link/verify" |
MAGIC_LINK_MAX_REQUESTS_PER_HOUR |
Rate limit for requests per email per hour | 5 |
MAGIC_LINK_ALLOWED_ROLES |
Comma-separated list of role IDs allowed to use magic links | (empty = all roles) |
MAGIC_LINK_DISALLOWED_ROLES |
Comma-separated list of role IDs not allowed to use magic links | (empty = no restrictions) |
PUBLIC_URL |
Your Directus instance URL | http://localhost:8055 |
DIRECTUS_INTERNAL_URL |
Internal URL for server-to-server calls (bypasses proxy) | PUBLIC_URL |
MAGIC_LINK_SITE_NAME |
Site name displayed in the demo interface | "Magic Link Demo" |
You can control which user roles can use magic links:
- If neither
MAGIC_LINK_ALLOWED_ROLESnorMAGIC_LINK_DISALLOWED_ROLESis set, all users can use magic links - If only
MAGIC_LINK_ALLOWED_ROLESis set, only users with those roles can use magic links - If only
MAGIC_LINK_DISALLOWED_ROLESis set, all users except those with the specified roles can use magic links - If both are set,
MAGIC_LINK_ALLOWED_ROLEStakes precedence (only users with allowed roles, and not in disallowed roles, can use magic links)
Example:
# Only allow the Student and Teacher roles
MAGIC_LINK_ALLOWED_ROLES=student-role-id,teacher-role-id
# Block the Admin role from using magic links
MAGIC_LINK_DISALLOWED_ROLES=admin-role-id
This extension integrates with Directus's logging system. To enable debug logs, set the LOG_LEVEL environment variable:
LOG_LEVEL=debug
This will output detailed information about magic link generation and verification, which can be helpful for troubleshooting.
- nodemailer - For handling email sending
- nanoid - For secure token generation
This project is licensed under the MIT License - see the LICENSE file for details.