AWS Lambda, Vercel, Cloudflare Workers, and more
Turalogin works perfectly with serverless environments. Start auth to send a magic link email, handle the callback when users click the link, and verify the token. No long-lived connections or shared state required - ideal for edge deployments.
Set up your environment variables or secrets.
1 # Your Turalogin API key from the dashboard 2 TURALOGIN_API_KEY=tl_live_xxxxxxxxxxxxx 3 4 # The URL where magic links will redirect to 5 # Development: 6 APP_LOGIN_VALIDATION_URL=http://localhost:3000/auth/callback 7 8 # Production (update for your domain): 9 # APP_LOGIN_VALIDATION_URL=https://myapp.com/auth/callback
A Lambda function that starts authentication and sends a magic link email.
1 import { APIGatewayProxyHandler } from "aws-lambda"; 2 3 const TURALOGIN_API_KEY = process.env.TURALOGIN_API_KEY!; 4 const TURALOGIN_URL = "https://api.turalogin.com/api/v1"; 5 const VALIDATION_URL = process.env.APP_LOGIN_VALIDATION_URL!; 6 7 async function turaloginRequest(endpoint: string, body: object) { 8 const response = await fetch(`${TURALOGIN_URL}${endpoint}`, { 9 method: "POST", 10 headers: { 11 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 12 "Content-Type": "application/json", 13 }, 14 body: JSON.stringify(body), 15 }); 16 17 const data = await response.json(); 18 19 if (!response.ok) { 20 throw { statusCode: response.status, body: data }; 21 } 22 23 return data; 24 } 25 26 export const startAuth: APIGatewayProxyHandler = async (event) => { 27 try { 28 const { email } = JSON.parse(event.body || "{}"); 29 30 if (!email) { 31 return { 32 statusCode: 400, 33 body: JSON.stringify({ error: "Email is required" }), 34 }; 35 } 36 37 await turaloginRequest("/auth/start", { 38 email, 39 validationUrl: VALIDATION_URL // Where the magic link redirects to 40 }); 41 42 return { 43 statusCode: 200, 44 headers: { "Content-Type": "application/json" }, 45 body: JSON.stringify({ 46 success: true, 47 message: "Check your email for the login link" 48 }), 49 }; 50 } catch (error: any) { 51 return { 52 statusCode: error.statusCode || 500, 53 body: JSON.stringify(error.body || { error: "Internal error" }), 54 }; 55 } 56 };
Handle the callback when users click the magic link, verify the token, and set a session cookie.
1 export const authCallback: APIGatewayProxyHandler = async (event) => { 2 try { 3 const token = event.queryStringParameters?.token; 4 5 if (!token) { 6 return { 7 statusCode: 302, 8 headers: { "Location": "/login?error=invalid_link" }, 9 body: "", 10 }; 11 } 12 13 const result = await turaloginRequest("/auth/verify", { sessionId: token }); 14 15 // Create your session token 16 const sessionToken = createSessionToken(result.user); 17 18 return { 19 statusCode: 302, 20 headers: { 21 "Location": "/dashboard", 22 "Set-Cookie": `session=${sessionToken}; HttpOnly; Secure; SameSite=Lax; Max-Age=604800; Path=/`, 23 }, 24 body: "", 25 }; 26 } catch (error: any) { 27 return { 28 statusCode: 302, 29 headers: { "Location": "/login?error=verification_failed" }, 30 body: "", 31 }; 32 } 33 }; 34 35 function createSessionToken(user: { id: string; email: string }): string { 36 // Use JWT or your preferred session token format 37 // For Lambda, consider using AWS KMS for signing 38 return Buffer.from(JSON.stringify(user)).toString("base64"); 39 }
A Cloudflare Worker that handles the full magic link auth flow.
1 export interface Env { 2 TURALOGIN_API_KEY: string; 3 APP_LOGIN_VALIDATION_URL: string; 4 } 5 6 async function turaloginRequest( 7 env: Env, 8 endpoint: string, 9 body: object 10 ) { 11 const response = await fetch(`https://api.turalogin.com/api/v1${endpoint}`, { 12 method: "POST", 13 headers: { 14 "Authorization": `Bearer ${env.TURALOGIN_API_KEY}`, 15 "Content-Type": "application/json", 16 }, 17 body: JSON.stringify(body), 18 }); 19 20 const data = await response.json() as any; 21 22 if (!response.ok) { 23 return { error: true, data, status: response.status }; 24 } 25 26 return { error: false, data }; 27 } 28 29 export default { 30 async fetch(request: Request, env: Env): Promise<Response> { 31 const url = new URL(request.url); 32 33 // CORS headers 34 const corsHeaders = { 35 "Access-Control-Allow-Origin": "*", 36 "Access-Control-Allow-Methods": "GET, POST, OPTIONS", 37 "Access-Control-Allow-Headers": "Content-Type", 38 }; 39 40 if (request.method === "OPTIONS") { 41 return new Response(null, { headers: corsHeaders }); 42 } 43 44 // Start auth - send magic link email 45 if (url.pathname === "/auth/start" && request.method === "POST") { 46 const { email } = await request.json() as { email: string }; 47 const result = await turaloginRequest(env, "/auth/start", { 48 email, 49 validationUrl: env.APP_LOGIN_VALIDATION_URL 50 }); 51 52 if (result.error) { 53 return new Response(JSON.stringify(result.data), { 54 status: result.status, 55 headers: { ...corsHeaders, "Content-Type": "application/json" }, 56 }); 57 } 58 59 return new Response(JSON.stringify({ 60 success: true, 61 message: "Check your email for the login link" 62 }), { 63 headers: { ...corsHeaders, "Content-Type": "application/json" }, 64 }); 65 } 66 67 // Callback - verify magic link token 68 if (url.pathname === "/auth/callback" && request.method === "GET") { 69 const token = url.searchParams.get("token"); 70 71 if (!token) { 72 return Response.redirect("/login?error=invalid_link", 302); 73 } 74 75 const result = await turaloginRequest(env, "/auth/verify", { 76 sessionId: token, 77 }); 78 79 if (result.error) { 80 return Response.redirect("/login?error=verification_failed", 302); 81 } 82 83 return new Response(null, { 84 status: 302, 85 headers: { 86 "Location": "/dashboard", 87 "Set-Cookie": `session=${result.data.token}; HttpOnly; Secure; SameSite=Lax; Max-Age=604800; Path=/`, 88 }, 89 }); 90 } 91 92 return new Response("Not Found", { status: 404 }); 93 }, 94 };
Edge functions for Vercel with magic link authentication.
1 import { NextRequest } from "next/server"; 2 3 export const config = { 4 runtime: "edge", 5 }; 6 7 const TURALOGIN_API_KEY = process.env.TURALOGIN_API_KEY!; 8 const VALIDATION_URL = process.env.APP_LOGIN_VALIDATION_URL!; 9 10 export default async function handler(req: NextRequest) { 11 if (req.method !== "POST") { 12 return new Response("Method not allowed", { status: 405 }); 13 } 14 15 const { email } = await req.json(); 16 17 if (!email) { 18 return Response.json({ error: "Email is required" }, { status: 400 }); 19 } 20 21 const response = await fetch("https://api.turalogin.com/api/v1/auth/start", { 22 method: "POST", 23 headers: { 24 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 25 "Content-Type": "application/json", 26 }, 27 body: JSON.stringify({ 28 email, 29 validationUrl: VALIDATION_URL 30 }), 31 }); 32 33 if (!response.ok) { 34 const error = await response.json(); 35 return Response.json(error, { status: response.status }); 36 } 37 38 return Response.json({ 39 success: true, 40 message: "Check your email for the login link" 41 }); 42 } 43 44 // api/auth/callback.ts - Handle magic link redirect 45 export default async function callbackHandler(req: NextRequest) { 46 const url = new URL(req.url); 47 const token = url.searchParams.get("token"); 48 49 if (!token) { 50 return Response.redirect(new URL("/login?error=invalid_link", req.url)); 51 } 52 53 const response = await fetch("https://api.turalogin.com/api/v1/auth/verify", { 54 method: "POST", 55 headers: { 56 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 57 "Content-Type": "application/json", 58 }, 59 body: JSON.stringify({ sessionId: token }), 60 }); 61 62 if (!response.ok) { 63 return Response.redirect(new URL("/login?error=verification_failed", req.url)); 64 } 65 66 const data = await response.json(); 67 68 return new Response(null, { 69 status: 302, 70 headers: { 71 "Location": "/dashboard", 72 "Set-Cookie": `session=${data.token}; HttpOnly; Secure; SameSite=Lax; Max-Age=604800; Path=/`, 73 }, 74 }); 75 }
Authentication handler for Netlify Functions.
1 import type { Handler } from "@netlify/functions"; 2 3 const TURALOGIN_API_KEY = process.env.TURALOGIN_API_KEY!; 4 const VALIDATION_URL = process.env.APP_LOGIN_VALIDATION_URL!; 5 6 export const handler: Handler = async (event) => { 7 // Handle CORS 8 if (event.httpMethod === "OPTIONS") { 9 return { 10 statusCode: 204, 11 headers: { 12 "Access-Control-Allow-Origin": "*", 13 "Access-Control-Allow-Methods": "POST, OPTIONS", 14 "Access-Control-Allow-Headers": "Content-Type", 15 }, 16 }; 17 } 18 19 if (event.httpMethod !== "POST") { 20 return { statusCode: 405, body: "Method not allowed" }; 21 } 22 23 try { 24 const { email } = JSON.parse(event.body || "{}"); 25 26 const response = await fetch("https://api.turalogin.com/api/v1/auth/start", { 27 method: "POST", 28 headers: { 29 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 30 "Content-Type": "application/json", 31 }, 32 body: JSON.stringify({ 33 email, 34 validationUrl: VALIDATION_URL 35 }), 36 }); 37 38 if (!response.ok) { 39 const error = await response.json(); 40 return { 41 statusCode: response.status, 42 headers: { "Content-Type": "application/json" }, 43 body: JSON.stringify(error), 44 }; 45 } 46 47 return { 48 statusCode: 200, 49 headers: { 50 "Content-Type": "application/json", 51 "Access-Control-Allow-Origin": "*", 52 }, 53 body: JSON.stringify({ 54 success: true, 55 message: "Check your email for the login link" 56 }), 57 }; 58 } catch (error) { 59 return { 60 statusCode: 500, 61 body: JSON.stringify({ error: "Internal server error" }), 62 }; 63 } 64 };
A complete serverless.yml configuration for AWS Lambda with magic link authentication.
1 # serverless.yml 2 service: my-app-auth 3 4 provider: 5 name: aws 6 runtime: nodejs18.x 7 environment: 8 TURALOGIN_API_KEY: ${env:TURALOGIN_API_KEY} 9 APP_LOGIN_VALIDATION_URL: ${env:APP_LOGIN_VALIDATION_URL} 10 11 functions: 12 authStart: 13 handler: handlers/auth.start 14 events: 15 - http: 16 path: auth/start 17 method: post 18 cors: true 19 20 authCallback: 21 handler: handlers/auth.callback 22 events: 23 - http: 24 path: auth/callback 25 method: get 26 27 authLogout: 28 handler: handlers/auth.logout 29 events: 30 - http: 31 path: auth/logout 32 method: post 33 cors: true 34 35 getMe: 36 handler: handlers/user.me 37 events: 38 - http: 39 path: api/me 40 method: get 41 cors: true 42 43 --- 44 // handlers/auth.ts 45 const TURALOGIN_URL = "https://api.turalogin.com/api/v1"; 46 const TURALOGIN_API_KEY = process.env.TURALOGIN_API_KEY!; 47 const VALIDATION_URL = process.env.APP_LOGIN_VALIDATION_URL!; 48 49 const headers = { 50 "Content-Type": "application/json", 51 "Access-Control-Allow-Origin": "*", 52 }; 53 54 async function turaloginRequest(endpoint: string, body: object) { 55 const response = await fetch(`${TURALOGIN_URL}${endpoint}`, { 56 method: "POST", 57 headers: { 58 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 59 "Content-Type": "application/json", 60 }, 61 body: JSON.stringify(body), 62 }); 63 return { data: await response.json(), status: response.status, ok: response.ok }; 64 } 65 66 export const start = async (event: any) => { 67 const { email } = JSON.parse(event.body || "{}"); 68 const result = await turaloginRequest("/auth/start", { 69 email, 70 validationUrl: VALIDATION_URL 71 }); 72 73 if (!result.ok) { 74 return { statusCode: result.status, headers, body: JSON.stringify(result.data) }; 75 } 76 77 return { 78 statusCode: 200, 79 headers, 80 body: JSON.stringify({ success: true, message: "Check your email for the login link" }) 81 }; 82 }; 83 84 export const callback = async (event: any) => { 85 const token = event.queryStringParameters?.token; 86 87 if (!token) { 88 return { statusCode: 302, headers: { "Location": "/login?error=invalid_link" }, body: "" }; 89 } 90 91 const result = await turaloginRequest("/auth/verify", { sessionId: token }); 92 93 if (!result.ok) { 94 return { statusCode: 302, headers: { "Location": "/login?error=verification_failed" }, body: "" }; 95 } 96 97 return { 98 statusCode: 302, 99 headers: { 100 "Location": "/dashboard", 101 "Set-Cookie": `session=${result.data.token}; HttpOnly; Secure; SameSite=Lax; Max-Age=604800; Path=/`, 102 }, 103 body: "", 104 }; 105 }; 106 107 export const logout = async () => { 108 return { 109 statusCode: 200, 110 headers: { 111 ...headers, 112 "Set-Cookie": "session=; HttpOnly; Secure; Max-Age=0; Path=/", 113 }, 114 body: JSON.stringify({ success: true }), 115 }; 116 };