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"
}'
Request Body
The type of link to generate. Options:
signup - Email confirmation link for new users
magiclink - Passwordless authentication link
recovery - Password recovery link
invite - User invitation link
email_change - Email change confirmation link
Email address of the user for whom to generate the link.
URL to redirect to after the link is used. If not provided, uses the default redirect URL.
Additional data to include with the link (for invitations or user creation).
Password for the user (required for some link types like recovery).
Response
The generated authentication link that can be sent to the user
One-time password for email verification (when applicable)
Hashed version of the token for verification purposes
Type of verification this link performs
The redirect URL that will be used after verification
200 - Link Generated Successfully
{
"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
400 - Invalid Request
401 - Unauthorized
404 - User Not Found
{
"code" : 400 ,
"msg" : "Invalid link type" ,
"details" : "Link type must be one of: signup, magiclink, recovery, invite, email_change"
}
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
Generate links only when needed
Use appropriate link types for different use cases
Include meaningful redirect URLs
Track link usage and expiration
Validate all input parameters
Use HTTPS for all redirect URLs
Implement proper access controls
Monitor for suspicious link generation patterns
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 );
});
});