Modern JavaScript runtimes
Modern runtimes work out of the box with Turalogin. No SDK required - just use native fetch APIs. Start auth to send a magic link email, handle the callback when users click the link, and verify the token. Clean, fast, and edge-friendly.
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
Create a simple Bun server endpoint to start authentication. This sends a magic link to the user's email.
1 const TURALOGIN_API_KEY = Bun.env.TURALOGIN_API_KEY!; 2 const TURALOGIN_URL = "https://api.turalogin.com/api/v1"; 3 const VALIDATION_URL = Bun.env.APP_LOGIN_VALIDATION_URL!; 4 5 async function startAuth(email: string) { 6 const response = await fetch(`${TURALOGIN_URL}/auth/start`, { 7 method: "POST", 8 headers: { 9 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 10 "Content-Type": "application/json", 11 }, 12 body: JSON.stringify({ 13 email, 14 validationUrl: VALIDATION_URL // Where the magic link redirects to 15 }), 16 }); 17 18 if (!response.ok) { 19 const error = await response.json(); 20 throw new Error(error.error || "Failed to start authentication"); 21 } 22 23 return response.json(); 24 } 25 26 const server = Bun.serve({ 27 port: 3000, 28 async fetch(req) { 29 const url = new URL(req.url); 30 31 if (url.pathname === "/auth/start" && req.method === "POST") { 32 try { 33 const { email } = await req.json(); 34 await startAuth(email); 35 36 return Response.json({ 37 success: true, 38 message: "Check your email for the login link" 39 }); 40 } catch (error) { 41 return Response.json( 42 { error: error.message }, 43 { status: 400 } 44 ); 45 } 46 } 47 48 return new Response("Not Found", { status: 404 }); 49 }, 50 }); 51 52 console.log(`Server running on port ${server.port}`);
Handle the callback when users click the magic link, verify the token, and set a session cookie.
1 async function verifyToken(sessionId: string) { 2 const response = await fetch(`${TURALOGIN_URL}/auth/verify`, { 3 method: "POST", 4 headers: { 5 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 6 "Content-Type": "application/json", 7 }, 8 body: JSON.stringify({ sessionId }), 9 }); 10 11 if (!response.ok) { 12 const error = await response.json(); 13 throw new Error(error.error || "Verification failed"); 14 } 15 16 return response.json(); 17 } 18 19 // In your fetch handler: 20 if (url.pathname === "/auth/callback" && req.method === "GET") { 21 const token = url.searchParams.get("token"); 22 23 if (!token) { 24 return Response.redirect("/login?error=invalid_link", 302); 25 } 26 27 try { 28 const result = await verifyToken(token); 29 30 // Create session token (using jose or similar) 31 const sessionToken = await createSessionToken(result.user); 32 33 // Redirect to dashboard with session cookie 34 return new Response(null, { 35 status: 302, 36 headers: { 37 "Location": "/dashboard", 38 "Set-Cookie": `session=${sessionToken}; HttpOnly; Secure; SameSite=Lax; Max-Age=604800; Path=/`, 39 }, 40 }); 41 } catch (error) { 42 return Response.redirect("/login?error=verification_failed", 302); 43 } 44 }
The same pattern works with Deno's native server.
1 const TURALOGIN_API_KEY = Deno.env.get("TURALOGIN_API_KEY")!; 2 const TURALOGIN_URL = "https://api.turalogin.com/api/v1"; 3 const VALIDATION_URL = Deno.env.get("APP_LOGIN_VALIDATION_URL")!; 4 5 interface TuraloginUser { 6 id: string; 7 email: string; 8 } 9 10 async function turaloginRequest<T>(endpoint: string, body: object): Promise<T> { 11 const response = await fetch(`${TURALOGIN_URL}${endpoint}`, { 12 method: "POST", 13 headers: { 14 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 15 "Content-Type": "application/json", 16 }, 17 body: JSON.stringify(body), 18 }); 19 20 const data = await response.json(); 21 22 if (!response.ok) { 23 throw new Error(data.error || "Turalogin API error"); 24 } 25 26 return data; 27 } 28 29 Deno.serve({ port: 3000 }, async (req) => { 30 const url = new URL(req.url); 31 32 // Start auth - send magic link 33 if (url.pathname === "/auth/start" && req.method === "POST") { 34 const { email } = await req.json(); 35 36 try { 37 await turaloginRequest("/auth/start", { 38 email, 39 validationUrl: VALIDATION_URL 40 }); 41 return Response.json({ 42 success: true, 43 message: "Check your email for the login link" 44 }); 45 } catch (error) { 46 return Response.json({ error: error.message }, { status: 400 }); 47 } 48 } 49 50 // Callback - verify magic link token 51 if (url.pathname === "/auth/callback" && req.method === "GET") { 52 const token = url.searchParams.get("token"); 53 54 if (!token) { 55 return Response.redirect("/login?error=invalid_link", 302); 56 } 57 58 try { 59 const result = await turaloginRequest<{ token: string; user: TuraloginUser }>( 60 "/auth/verify", 61 { sessionId: token } 62 ); 63 64 return new Response(null, { 65 status: 302, 66 headers: { 67 "Location": "/dashboard", 68 "Set-Cookie": `session=${result.token}; HttpOnly; Secure; SameSite=Lax; Path=/`, 69 }, 70 }); 71 } catch (error) { 72 return Response.redirect("/login?error=verification_failed", 302); 73 } 74 } 75 76 return new Response("Not Found", { status: 404 }); 77 });
Using the Hono framework for a cleaner routing experience.
1 import { Hono } from "hono"; 2 import { setCookie, getCookie } from "hono/cookie"; 3 4 const app = new Hono(); 5 6 const TURALOGIN_API_KEY = process.env.TURALOGIN_API_KEY!; 7 const TURALOGIN_URL = "https://api.turalogin.com/api/v1"; 8 const VALIDATION_URL = process.env.APP_LOGIN_VALIDATION_URL!; 9 10 // Turalogin client 11 const turalogin = { 12 async startAuth(email: string) { 13 const res = await fetch(`${TURALOGIN_URL}/auth/start`, { 14 method: "POST", 15 headers: { 16 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 17 "Content-Type": "application/json", 18 }, 19 body: JSON.stringify({ 20 email, 21 validationUrl: VALIDATION_URL 22 }), 23 }); 24 if (!res.ok) throw new Error((await res.json()).error); 25 return res.json(); 26 }, 27 28 async verifyToken(sessionId: string) { 29 const res = await fetch(`${TURALOGIN_URL}/auth/verify`, { 30 method: "POST", 31 headers: { 32 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 33 "Content-Type": "application/json", 34 }, 35 body: JSON.stringify({ sessionId }), 36 }); 37 if (!res.ok) throw new Error((await res.json()).error); 38 return res.json(); 39 }, 40 }; 41 42 // Routes 43 app.post("/auth/start", async (c) => { 44 const { email } = await c.req.json(); 45 46 try { 47 await turalogin.startAuth(email); 48 return c.json({ success: true, message: "Check your email for the login link" }); 49 } catch (error) { 50 return c.json({ error: error.message }, 400); 51 } 52 }); 53 54 app.get("/auth/callback", async (c) => { 55 const token = c.req.query("token"); 56 57 if (!token) { 58 return c.redirect("/login?error=invalid_link"); 59 } 60 61 try { 62 const result = await turalogin.verifyToken(token); 63 64 setCookie(c, "session", result.token, { 65 httpOnly: true, 66 secure: true, 67 sameSite: "Lax", 68 maxAge: 60 * 60 * 24 * 7, 69 path: "/", 70 }); 71 72 return c.redirect("/dashboard"); 73 } catch (error) { 74 return c.redirect("/login?error=verification_failed"); 75 } 76 }); 77 78 // Protected route 79 app.get("/api/me", async (c) => { 80 const session = getCookie(c, "session"); 81 if (!session) { 82 return c.json({ error: "Unauthorized" }, 401); 83 } 84 85 // Validate session and get user 86 // ... 87 88 return c.json({ user: { /* ... */ } }); 89 }); 90 91 export default app;
Create middleware for protecting routes.
1 import { Context, Next } from "hono"; 2 import { getCookie } from "hono/cookie"; 3 4 // Simple JWT validation (use a proper library in production) 5 async function validateSession(token: string) { 6 // Implement your session validation 7 // This could be JWT verification, database lookup, etc. 8 return { id: "user-id", email: "user@example.com" }; 9 } 10 11 export async function requireAuth(c: Context, next: Next) { 12 const sessionToken = getCookie(c, "session"); 13 14 if (!sessionToken) { 15 return c.json({ error: "Authentication required" }, 401); 16 } 17 18 try { 19 const user = await validateSession(sessionToken); 20 c.set("user", user); 21 await next(); 22 } catch { 23 return c.json({ error: "Invalid session" }, 401); 24 } 25 } 26 27 // Usage in Hono: 28 // app.get("/api/me", requireAuth, (c) => { 29 // const user = c.get("user"); 30 // return c.json({ user }); 31 // }); 32 33 // Or protect a group of routes: 34 // const api = new Hono(); 35 // api.use("*", requireAuth); 36 // api.get("/me", (c) => c.json({ user: c.get("user") })); 37 // app.route("/api", api);
A complete Bun application using the Elysia framework with magic link authentication.
1 import { Elysia, t } from "elysia"; 2 import { cookie } from "@elysiajs/cookie"; 3 4 const TURALOGIN_API_KEY = Bun.env.TURALOGIN_API_KEY!; 5 const TURALOGIN_URL = "https://api.turalogin.com/api/v1"; 6 const VALIDATION_URL = Bun.env.APP_LOGIN_VALIDATION_URL!; 7 8 // Turalogin service 9 class TuraloginService { 10 private async request<T>(endpoint: string, body: object): Promise<T> { 11 const response = await fetch(`${TURALOGIN_URL}${endpoint}`, { 12 method: "POST", 13 headers: { 14 "Authorization": `Bearer ${TURALOGIN_API_KEY}`, 15 "Content-Type": "application/json", 16 }, 17 body: JSON.stringify(body), 18 }); 19 20 const data = await response.json(); 21 22 if (!response.ok) { 23 throw new Error(data.error || "API error"); 24 } 25 26 return data; 27 } 28 29 startAuth(email: string) { 30 return this.request("/auth/start", { 31 email, 32 validationUrl: VALIDATION_URL 33 }); 34 } 35 36 verifyToken(sessionId: string) { 37 return this.request<{ token: string; user: { id: string; email: string } }>( 38 "/auth/verify", 39 { sessionId } 40 ); 41 } 42 } 43 44 const turalogin = new TuraloginService(); 45 46 // Create app 47 const app = new Elysia() 48 .use(cookie()) 49 .decorate("turalogin", turalogin) 50 51 // Start auth - send magic link email 52 .post( 53 "/auth/start", 54 async ({ body, turalogin }) => { 55 await turalogin.startAuth(body.email); 56 return { success: true, message: "Check your email for the login link" }; 57 }, 58 { 59 body: t.Object({ 60 email: t.String({ format: "email" }), 61 }), 62 } 63 ) 64 65 // Callback - handle magic link redirect 66 .get( 67 "/auth/callback", 68 async ({ query, turalogin, cookie: { session }, redirect }) => { 69 const { token } = query; 70 71 if (!token) { 72 return redirect("/login?error=invalid_link"); 73 } 74 75 try { 76 const result = await turalogin.verifyToken(token); 77 78 session.set({ 79 value: result.token, 80 httpOnly: true, 81 secure: true, 82 sameSite: "lax", 83 maxAge: 60 * 60 * 24 * 7, 84 path: "/", 85 }); 86 87 return redirect("/dashboard"); 88 } catch (error) { 89 return redirect("/login?error=verification_failed"); 90 } 91 }, 92 { 93 query: t.Object({ 94 token: t.Optional(t.String()), 95 }), 96 } 97 ) 98 99 .post("/auth/logout", ({ cookie: { session } }) => { 100 session.remove(); 101 return { success: true }; 102 }) 103 104 // Protected routes 105 .guard( 106 { 107 beforeHandle: ({ cookie: { session }, set }) => { 108 if (!session.value) { 109 set.status = 401; 110 return { error: "Authentication required" }; 111 } 112 }, 113 }, 114 (app) => 115 app 116 .get("/api/me", ({ cookie: { session } }) => { 117 // Decode session and return user 118 return { user: { /* decoded from session */ } }; 119 }) 120 .get("/api/dashboard", () => { 121 return { message: "Welcome to the dashboard!" }; 122 }) 123 ) 124 125 .listen(3000); 126 127 console.log(`🚀 Server running at ${app.server?.url}`); 128 129 export type App = typeof app;