Express, Fastify, Hapi, and more
Turalogin integrates seamlessly with any Node.js backend. Use a single service file to call Turalogin from your login endpoint. The user receives a magic link email that redirects to your callback URL. Works the same whether you're running Express, Fastify, Hapi, or a custom HTTP server.
Set up your environment variables for local development and production.
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 10 11 # Session secret for express-session 12 SESSION_SECRET=your-session-secret-here
Create a route to initiate authentication. This sends a magic link to the user's email.
1 const express = require('express'); 2 const router = express.Router(); 3 4 const TURALOGIN_API_KEY = process.env.TURALOGIN_API_KEY; 5 const TURALOGIN_API_URL = 'https://api.turalogin.com/api/v1'; 6 const VALIDATION_URL = process.env.APP_LOGIN_VALIDATION_URL; 7 8 router.post('/start', async (req, res) => { 9 try { 10 const { email } = req.body; 11 12 if (!email) { 13 return res.status(400).json({ error: 'Email is required' }); 14 } 15 16 const response = await fetch(`${TURALOGIN_API_URL}/auth/start`, { 17 method: 'POST', 18 headers: { 19 'Authorization': `Bearer ${TURALOGIN_API_KEY}`, 20 'Content-Type': 'application/json', 21 }, 22 body: JSON.stringify({ 23 email, 24 validationUrl: VALIDATION_URL // Where the magic link redirects to 25 }), 26 }); 27 28 const data = await response.json(); 29 30 if (!response.ok) { 31 return res.status(response.status).json(data); 32 } 33 34 res.json({ 35 success: true, 36 message: 'Check your email for the login link' 37 }); 38 39 } catch (error) { 40 console.error('Auth start error:', error); 41 res.status(500).json({ error: 'Internal server error' }); 42 } 43 }); 44 45 module.exports = router;
Create a callback route that receives the token from the magic link and verifies it.
1 // GET /auth/callback?token=xxx - Handle magic link redirect 2 router.get('/callback', async (req, res) => { 3 try { 4 const { token } = req.query; 5 6 if (!token) { 7 return res.redirect('/login?error=invalid_link'); 8 } 9 10 // Verify the token with Turalogin 11 const response = await fetch(`${TURALOGIN_API_URL}/auth/verify`, { 12 method: 'POST', 13 headers: { 14 'Authorization': `Bearer ${TURALOGIN_API_KEY}`, 15 'Content-Type': 'application/json', 16 }, 17 body: JSON.stringify({ sessionId: token }), 18 }); 19 20 const data = await response.json(); 21 22 if (!response.ok) { 23 return res.redirect('/login?error=verification_failed'); 24 } 25 26 const { token: jwt, user } = data; 27 28 // Create your own session 29 req.session.userId = user.id; 30 req.session.email = user.email; 31 req.session.turaloginToken = jwt; 32 33 // Redirect to dashboard 34 res.redirect('/dashboard'); 35 36 } catch (error) { 37 console.error('Auth callback error:', error); 38 res.redirect('/login?error=server_error'); 39 } 40 });
The same pattern works with Fastify's request/reply API.
1 import Fastify from 'fastify'; 2 import fastifySession from '@fastify/session'; 3 import fastifyCookie from '@fastify/cookie'; 4 5 const fastify = Fastify(); 6 7 const TURALOGIN_API_KEY = process.env.TURALOGIN_API_KEY!; 8 const TURALOGIN_API_URL = 'https://api.turalogin.com/api/v1'; 9 const VALIDATION_URL = process.env.APP_LOGIN_VALIDATION_URL!; 10 11 // Register plugins 12 fastify.register(fastifyCookie); 13 fastify.register(fastifySession, { 14 secret: process.env.SESSION_SECRET!, 15 cookie: { secure: process.env.NODE_ENV === 'production' }, 16 }); 17 18 // Start auth - send magic link 19 fastify.post('/auth/start', async (request, reply) => { 20 const { email } = request.body as { email: string }; 21 22 const response = await fetch(`${TURALOGIN_API_URL}/auth/start`, { 23 method: 'POST', 24 headers: { 25 'Authorization': `Bearer ${TURALOGIN_API_KEY}`, 26 'Content-Type': 'application/json', 27 }, 28 body: JSON.stringify({ 29 email, 30 validationUrl: VALIDATION_URL 31 }), 32 }); 33 34 if (!response.ok) { 35 const error = await response.json(); 36 return reply.status(response.status).send(error); 37 } 38 39 return { success: true, message: 'Check your email for the login link' }; 40 }); 41 42 // Callback - verify magic link token 43 fastify.get('/auth/callback', async (request, reply) => { 44 const { token } = request.query as { token: string }; 45 46 const response = await fetch(`${TURALOGIN_API_URL}/auth/verify`, { 47 method: 'POST', 48 headers: { 49 'Authorization': `Bearer ${TURALOGIN_API_KEY}`, 50 'Content-Type': 'application/json', 51 }, 52 body: JSON.stringify({ sessionId: token }), 53 }); 54 55 if (!response.ok) { 56 return reply.redirect('/login?error=verification_failed'); 57 } 58 59 const data = await response.json(); 60 request.session.set('user', data.user); 61 62 return reply.redirect('/dashboard'); 63 });
Extract Turalogin calls into a reusable service module for cleaner code.
1 class TuraloginService { 2 constructor(apiKey, validationUrl) { 3 this.apiKey = apiKey; 4 this.validationUrl = validationUrl; 5 this.baseUrl = 'https://api.turalogin.com/api/v1'; 6 } 7 8 async request(endpoint, body) { 9 const response = await fetch(`${this.baseUrl}${endpoint}`, { 10 method: 'POST', 11 headers: { 12 'Authorization': `Bearer ${this.apiKey}`, 13 'Content-Type': 'application/json', 14 }, 15 body: JSON.stringify(body), 16 }); 17 18 const data = await response.json(); 19 20 if (!response.ok) { 21 const error = new Error(data.error || 'Turalogin API error'); 22 error.status = response.status; 23 error.code = data.code; 24 throw error; 25 } 26 27 return data; 28 } 29 30 async startAuth(email) { 31 return this.request('/auth/start', { 32 email, 33 validationUrl: this.validationUrl 34 }); 35 } 36 37 async verifyToken(sessionId) { 38 return this.request('/auth/verify', { sessionId }); 39 } 40 } 41 42 module.exports = new TuraloginService( 43 process.env.TURALOGIN_API_KEY, 44 process.env.APP_LOGIN_VALIDATION_URL 45 );
Centralized error handling for Turalogin-related errors.
1 function turaloginErrorHandler(err, req, res, next) { 2 // Handle Turalogin-specific errors 3 if (err.code) { 4 switch (err.code) { 5 case 'INVALID_EMAIL': 6 return res.status(400).json({ 7 error: 'Please provide a valid email address', 8 code: err.code, 9 }); 10 11 case 'SESSION_EXPIRED': 12 return res.status(400).json({ 13 error: 'Login link has expired. Please try again.', 14 code: err.code, 15 }); 16 17 case 'INVALID_TOKEN': 18 return res.status(400).json({ 19 error: 'Invalid login link. Please request a new one.', 20 code: err.code, 21 }); 22 23 case 'RATE_LIMITED': 24 return res.status(429).json({ 25 error: 'Too many attempts. Please wait a moment.', 26 code: err.code, 27 retryAfter: err.retryAfter, 28 }); 29 30 default: 31 console.error('Turalogin error:', err); 32 return res.status(err.status || 500).json({ 33 error: 'Authentication error. Please try again.', 34 code: err.code, 35 }); 36 } 37 } 38 39 // Pass to default error handler 40 next(err); 41 } 42 43 module.exports = turaloginErrorHandler; 44 45 // Usage in app.js: 46 // const errorHandler = require('./middleware/errorHandler'); 47 // app.use(errorHandler);
A complete Express application with authentication routes, session management, and protected endpoints.
1 const express = require('express'); 2 const session = require('express-session'); 3 const cors = require('cors'); 4 const turalogin = require('./services/turalogin'); 5 const errorHandler = require('./middleware/errorHandler'); 6 7 const app = express(); 8 9 // Middleware 10 app.use(cors({ origin: process.env.FRONTEND_URL, credentials: true })); 11 app.use(express.json()); 12 app.use(session({ 13 secret: process.env.SESSION_SECRET, 14 resave: false, 15 saveUninitialized: false, 16 cookie: { 17 secure: process.env.NODE_ENV === 'production', 18 httpOnly: true, 19 maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days 20 }, 21 })); 22 23 // Start auth - send magic link email 24 app.post('/auth/start', async (req, res, next) => { 25 try { 26 const { email } = req.body; 27 await turalogin.startAuth(email); 28 res.json({ success: true, message: 'Check your email for the login link' }); 29 } catch (err) { 30 next(err); 31 } 32 }); 33 34 // Callback - handle magic link redirect 35 app.get('/auth/callback', async (req, res, next) => { 36 try { 37 const { token } = req.query; 38 39 if (!token) { 40 return res.redirect('/login?error=invalid_link'); 41 } 42 43 const { token: jwt, user } = await turalogin.verifyToken(token); 44 45 // Store in session 46 req.session.userId = user.id; 47 req.session.email = user.email; 48 req.session.token = jwt; 49 50 res.redirect('/dashboard'); 51 } catch (err) { 52 console.error('Auth callback error:', err); 53 res.redirect('/login?error=verification_failed'); 54 } 55 }); 56 57 app.post('/auth/logout', (req, res) => { 58 req.session.destroy((err) => { 59 if (err) { 60 return res.status(500).json({ error: 'Logout failed' }); 61 } 62 res.clearCookie('connect.sid'); 63 res.json({ success: true }); 64 }); 65 }); 66 67 // Auth middleware 68 function requireAuth(req, res, next) { 69 if (!req.session.userId) { 70 return res.status(401).json({ error: 'Authentication required' }); 71 } 72 next(); 73 } 74 75 // Protected routes 76 app.get('/api/me', requireAuth, (req, res) => { 77 res.json({ 78 id: req.session.userId, 79 email: req.session.email, 80 }); 81 }); 82 83 app.get('/api/dashboard', requireAuth, (req, res) => { 84 res.json({ message: 'Welcome to the dashboard!' }); 85 }); 86 87 // Error handling 88 app.use(errorHandler); 89 90 // Start server 91 const PORT = process.env.PORT || 3000; 92 app.listen(PORT, () => { 93 console.log(`Server running on port ${PORT}`); 94 });