curl -X POST "http://localhost:8080/admin/generate_link" \
-H "Authorization: Bearer service_role_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard"
}'
const response = await fetch('http://localhost:8080/admin/generate_link', {
method: 'POST',
headers: {
'Authorization': 'Bearer service_role_key_here',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'magiclink',
email: 'user@example.com',
redirect_to: 'https://yourapp.com/dashboard'
}),
});
const linkData = await response.json();
import requests
headers = {
'Authorization': 'Bearer service_role_key_here',
'Content-Type': 'application/json'
}
data = {
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard"
}
response = requests.post(
'http://localhost:8080/admin/generate_link',
headers=headers,
json=data
)
link_data = response.json()
package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
data := map[string]string{
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST",
"http://localhost:8080/admin/generate_link",
bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer service_role_key_here")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
// Handle response...
}
{
"action_link": "http://localhost:8080/verify?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&type=magiclink&redirect_to=https%3A//yourapp.com/dashboard",
"email_otp": "123456",
"hashed_token": "sha256_hash_of_token",
"verification_type": "magiclink",
"redirect_to": "https://yourapp.com/dashboard"
}
Admin
Generate Link
Generate authentication links for users (admin only)
POST
/
admin
/
generate_link
curl -X POST "http://localhost:8080/admin/generate_link" \
-H "Authorization: Bearer service_role_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard"
}'
const response = await fetch('http://localhost:8080/admin/generate_link', {
method: 'POST',
headers: {
'Authorization': 'Bearer service_role_key_here',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'magiclink',
email: 'user@example.com',
redirect_to: 'https://yourapp.com/dashboard'
}),
});
const linkData = await response.json();
import requests
headers = {
'Authorization': 'Bearer service_role_key_here',
'Content-Type': 'application/json'
}
data = {
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard"
}
response = requests.post(
'http://localhost:8080/admin/generate_link',
headers=headers,
json=data
)
link_data = response.json()
package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
data := map[string]string{
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST",
"http://localhost:8080/admin/generate_link",
bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer service_role_key_here")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
// Handle response...
}
{
"action_link": "http://localhost:8080/verify?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&type=magiclink&redirect_to=https%3A//yourapp.com/dashboard",
"email_otp": "123456",
"hashed_token": "sha256_hash_of_token",
"verification_type": "magiclink",
"redirect_to": "https://yourapp.com/dashboard"
}
Generate secure authentication links for users. This endpoint allows administrators to create magic links, recovery links, or invitation links that can be sent to users via custom channels.
This endpoint requires service role authentication. Only use the service role key on your backend servers, never in client-side code.
curl -X POST "http://localhost:8080/admin/generate_link" \
-H "Authorization: Bearer service_role_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard"
}'
const response = await fetch('http://localhost:8080/admin/generate_link', {
method: 'POST',
headers: {
'Authorization': 'Bearer service_role_key_here',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'magiclink',
email: 'user@example.com',
redirect_to: 'https://yourapp.com/dashboard'
}),
});
const linkData = await response.json();
import requests
headers = {
'Authorization': 'Bearer service_role_key_here',
'Content-Type': 'application/json'
}
data = {
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard"
}
response = requests.post(
'http://localhost:8080/admin/generate_link',
headers=headers,
json=data
)
link_data = response.json()
package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
data := map[string]string{
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST",
"http://localhost:8080/admin/generate_link",
bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer service_role_key_here")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
// Handle response...
}
Request Body
string
required
The type of link to generate.Options:
signup- Email confirmation link for new usersmagiclink- Passwordless authentication linkrecovery- Password recovery linkinvite- User invitation linkemail_change- Email change confirmation link
string
required
Email address of the user for whom to generate the link.
string
URL to redirect to after the link is used. If not provided, uses the default redirect URL.
object
Additional data to include with the link (for invitations or user creation).
string
Password for the user (required for some link types like recovery).
Response
string
The generated authentication link that can be sent to the user
string
One-time password for email verification (when applicable)
string
Hashed version of the token for verification purposes
string
Type of verification this link performs
string
The redirect URL that will be used after verification
{
"action_link": "http://localhost:8080/verify?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&type=magiclink&redirect_to=https%3A//yourapp.com/dashboard",
"email_otp": "123456",
"hashed_token": "sha256_hash_of_token",
"verification_type": "magiclink",
"redirect_to": "https://yourapp.com/dashboard"
}
Error Responses
{
"code": 400,
"msg": "Invalid link type",
"details": "Link type must be one of: signup, magiclink, recovery, invite, email_change"
}
{
"code": 401,
"msg": "Invalid service role key",
"details": "Please provide a valid service role key"
}
{
"code": 404,
"msg": "User not found",
"details": "No user found with the provided email address"
}
Link Types
Magic Link
Generate a passwordless authentication link:{
"type": "magiclink",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/dashboard"
}
Password Recovery Link
Generate a password recovery link:{
"type": "recovery",
"email": "user@example.com",
"redirect_to": "https://yourapp.com/reset-password"
}
Email Confirmation Link
Generate an email confirmation link for new users:{
"type": "signup",
"email": "newuser@example.com",
"redirect_to": "https://yourapp.com/welcome"
}
User Invitation Link
Generate an invitation link for new users:{
"type": "invite",
"email": "invite@example.com",
"data": {
"first_name": "John",
"last_name": "Doe",
"role": "team_member"
},
"redirect_to": "https://yourapp.com/setup"
}
Email Change Confirmation
Generate a link to confirm email address changes:{
"type": "email_change",
"email": "newemail@example.com",
"redirect_to": "https://yourapp.com/profile"
}
Implementation Examples
React Admin Link Generator
import { useState } from 'react';
function AdminLinkGenerator() {
const [linkType, setLinkType] = useState('magiclink');
const [email, setEmail] = useState('');
const [redirectTo, setRedirectTo] = useState('');
const [loading, setLoading] = useState(false);
const [generatedLink, setGeneratedLink] = useState('');
const [error, setError] = useState('');
const handleGenerate = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
setGeneratedLink('');
try {
const response = await fetch('/api/admin/generate_link', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.REACT_APP_SERVICE_ROLE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: linkType,
email,
redirect_to: redirectTo || undefined
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.msg || 'Failed to generate link');
}
const linkData = await response.json();
setGeneratedLink(linkData.action_link);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(generatedLink);
alert('Link copied to clipboard!');
} catch (err) {
console.error('Failed to copy link:', err);
}
};
return (
<div className="admin-link-generator">
<h2>Generate Authentication Link</h2>
{error && (
<div className="error-message">
{error}
</div>
)}
<form onSubmit={handleGenerate} className="link-form">
<div className="form-group">
<label htmlFor="linkType">Link Type</label>
<select
id="linkType"
value={linkType}
onChange={(e) => setLinkType(e.target.value)}
required
>
<option value="magiclink">Magic Link</option>
<option value="recovery">Password Recovery</option>
<option value="signup">Email Confirmation</option>
<option value="invite">User Invitation</option>
<option value="email_change">Email Change</option>
</select>
</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="user@example.com"
required
/>
</div>
<div className="form-group">
<label htmlFor="redirectTo">Redirect URL (optional)</label>
<input
type="url"
id="redirectTo"
value={redirectTo}
onChange={(e) => setRedirectTo(e.target.value)}
placeholder="https://yourapp.com/dashboard"
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Generating...' : 'Generate Link'}
</button>
</form>
{generatedLink && (
<div className="generated-link">
<h3>Generated Link</h3>
<div className="link-container">
<input
type="text"
value={generatedLink}
readOnly
className="link-input"
/>
<button onClick={copyToClipboard} className="copy-button">
Copy
</button>
</div>
<p className="link-info">
This link will expire in 1 hour and can only be used once.
</p>
</div>
)}
</div>
);
}
export default AdminLinkGenerator;
Bulk Link Generation
import { useState } from 'react';
function BulkLinkGenerator() {
const [emails, setEmails] = useState('');
const [linkType, setLinkType] = useState('invite');
const [redirectTo, setRedirectTo] = useState('');
const [loading, setLoading] = useState(false);
const [results, setResults] = useState([]);
const handleBulkGenerate = async (e) => {
e.preventDefault();
setLoading(true);
setResults([]);
const emailList = emails.split('\n').filter(email => email.trim());
const generatedLinks = [];
for (const email of emailList) {
try {
const response = await fetch('/api/admin/generate_link', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.REACT_APP_SERVICE_ROLE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: linkType,
email: email.trim(),
redirect_to: redirectTo || undefined
}),
});
if (response.ok) {
const linkData = await response.json();
generatedLinks.push({
email: email.trim(),
success: true,
link: linkData.action_link
});
} else {
const errorData = await response.json();
generatedLinks.push({
email: email.trim(),
success: false,
error: errorData.msg
});
}
} catch (err) {
generatedLinks.push({
email: email.trim(),
success: false,
error: err.message
});
}
}
setResults(generatedLinks);
setLoading(false);
};
const exportResults = () => {
const csvContent = [
'Email,Status,Link,Error',
...results.map(result =>
`${result.email},${result.success ? 'Success' : 'Failed'},${result.link || ''},${result.error || ''}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `bulk_links_${Date.now()}.csv`;
a.click();
window.URL.revokeObjectURL(url);
};
return (
<div className="bulk-link-generator">
<h2>Bulk Link Generation</h2>
<form onSubmit={handleBulkGenerate} className="bulk-form">
<div className="form-group">
<label htmlFor="emails">Email Addresses (one per line)</label>
<textarea
id="emails"
value={emails}
onChange={(e) => setEmails(e.target.value)}
placeholder="user1@example.com user2@example.com user3@example.com"
rows={10}
required
/>
</div>
<div className="form-group">
<label htmlFor="linkType">Link Type</label>
<select
id="linkType"
value={linkType}
onChange={(e) => setLinkType(e.target.value)}
>
<option value="invite">User Invitation</option>
<option value="magiclink">Magic Link</option>
<option value="recovery">Password Recovery</option>
<option value="signup">Email Confirmation</option>
</select>
</div>
<div className="form-group">
<label htmlFor="redirectTo">Redirect URL (optional)</label>
<input
type="url"
id="redirectTo"
value={redirectTo}
onChange={(e) => setRedirectTo(e.target.value)}
placeholder="https://yourapp.com/welcome"
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Generating Links...' : 'Generate All Links'}
</button>
</form>
{results.length > 0 && (
<div className="results">
<div className="results-header">
<h3>Results ({results.length} total)</h3>
<button onClick={exportResults} className="export-button">
Export CSV
</button>
</div>
<div className="results-summary">
<span className="success-count">
✅ {results.filter(r => r.success).length} successful
</span>
<span className="error-count">
❌ {results.filter(r => !r.success).length} failed
</span>
</div>
<div className="results-list">
{results.map((result, index) => (
<div key={index} className={`result-item ${result.success ? 'success' : 'error'}`}>
<div className="result-email">{result.email}</div>
{result.success ? (
<div className="result-link">
<input type="text" value={result.link} readOnly />
<button onClick={() => navigator.clipboard.writeText(result.link)}>
Copy
</button>
</div>
) : (
<div className="result-error">{result.error}</div>
)}
</div>
))}
</div>
</div>
)}
</div>
);
}
export default BulkLinkGenerator;
Node.js Backend Handler
const express = require('express');
const { body, validationResult } = require('express-validator');
const router = express.Router();
// Middleware to verify service role
const verifyServiceRole = (req, res, next) => {
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1];
if (!token || token !== process.env.SERVICE_ROLE_KEY) {
return res.status(401).json({
code: 401,
msg: 'Invalid service role key',
details: 'Please provide a valid service role key'
});
}
next();
};
router.post('/admin/generate_link', [
verifyServiceRole,
body('type').isIn(['signup', 'magiclink', 'recovery', 'invite', 'email_change']),
body('email').isEmail().normalizeEmail(),
body('redirect_to').optional().isURL(),
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 { type, email, redirect_to, data, password } = req.body;
// Call Strike Auth Service
const response = await fetch(`${process.env.AUTH_SERVICE_URL}/admin/generate_link`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SERVICE_ROLE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type,
email,
redirect_to,
data,
password
}),
});
const result = await response.json();
if (!response.ok) {
return res.status(response.status).json(result);
}
// Log link generation
console.log(`Link generated: ${type} for ${email}`);
res.json(result);
} catch (error) {
console.error('Link generation error:', error);
res.status(500).json({
code: 500,
msg: 'Internal server error',
details: 'Please try again later'
});
}
});
module.exports = router;
Use Cases
Custom Email Templates
Generate links for use in custom email templates:async function sendCustomInvitation(email, userData) {
// Generate invitation link
const linkResponse = await fetch('/api/admin/generate_link', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SERVICE_ROLE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'invite',
email,
data: userData,
redirect_to: 'https://yourapp.com/welcome'
}),
});
const { action_link } = await linkResponse.json();
// Send custom email with the generated link
await sendEmail({
to: email,
subject: 'Welcome to Strike!',
template: 'custom-invitation',
data: {
inviteLink: action_link,
userData
}
});
}
Integration with External Systems
Generate links for integration with CRM or marketing tools:async function syncUserWithCRM(userData) {
// Generate magic link for user
const linkResponse = await fetch('/api/admin/generate_link', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SERVICE_ROLE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'magiclink',
email: userData.email,
redirect_to: 'https://yourapp.com/dashboard'
}),
});
const { action_link } = await linkResponse.json();
// Update CRM with login link
await updateCRMContact(userData.email, {
login_link: action_link,
last_updated: new Date().toISOString()
});
}
Security Considerations
- Service Role Protection: Never expose service role keys in client-side code
- Link Expiration: Generated links expire after 1 hour by default
- Single Use: Links can only be used once for security
- Rate Limiting: Implement rate limits for link generation
- Audit Logging: Log all link generation activities
Best Practices
Link Management
Link Management
- Generate links only when needed
- Use appropriate link types for different use cases
- Include meaningful redirect URLs
- Track link usage and expiration
Security
Security
- Validate all input parameters
- Use HTTPS for all redirect URLs
- Implement proper access controls
- Monitor for suspicious link generation patterns
User Experience
User Experience
- Provide clear instructions with generated links
- Use descriptive redirect URLs
- Handle expired links gracefully
- Offer alternative authentication methods
Testing
Unit Tests
describe('POST /admin/generate_link', () => {
test('should generate magic link for existing user', async () => {
const response = await request(app)
.post('/admin/generate_link')
.set('Authorization', 'Bearer valid_service_role_key')
.send({
type: 'magiclink',
email: 'existing@example.com',
redirect_to: 'https://example.com/dashboard'
})
.expect(200);
expect(response.body.action_link).toContain('verify?token=');
expect(response.body.verification_type).toBe('magiclink');
});
test('should generate invitation link with user data', async () => {
const response = await request(app)
.post('/admin/generate_link')
.set('Authorization', 'Bearer valid_service_role_key')
.send({
type: 'invite',
email: 'new@example.com',
data: { first_name: 'John', role: 'admin' }
})
.expect(200);
expect(response.body.action_link).toBeDefined();
});
test('should reject invalid service role', async () => {
await request(app)
.post('/admin/generate_link')
.set('Authorization', 'Bearer invalid_key')
.send({
type: 'magiclink',
email: 'test@example.com'
})
.expect(401);
});
});
Related Endpoints
Create User
Create new user accounts
Update User
Update user information
Invite User
Send user invitations
Verify Link
Verify generated links
⌘I