Turalogin is built for backends. If your server can make an HTTP request, you can add email-based authentication in minutes. No redirects, no hosted UI, no frontend lock-in.
Every example below follows the same pattern: start auth, verify email, exchange proof for a token. The framework fades away.
Copy a prompt to Cursor, Claude, or ChatGPT to scaffold authentication instantly.
More framework-specific prompts available on each integration page below.
Works naturally with API routes, route handlers, or server actions. Your Next.js backend calls Turalogin to start auth, then exchanges the proof server-side and sets its own session cookie.
This is the primary reference implementation and what powers the Turalogin site itself.
// app/api/auth/start/route.ts
import { turalogin } from '@/lib/turalogin';
export async function POST(req: Request) {
const { email } = await req.json();
// Start authentication - sends OTP to user's email
const { sessionId } = await turalogin.startAuth({ email });
return Response.json({ sessionId });
}
// app/api/auth/verify/route.ts
export async function POST(req: Request) {
const { sessionId, code } = await req.json();
// Exchange the code for a JWT
const { token, user } = await turalogin.verifyCode({ sessionId, code });
// Create your own session
const session = await createSession(user);
return Response.json({ success: true }, {
headers: { 'Set-Cookie': `session=${session.id}; HttpOnly; Secure` }
});
}Drop-in for any Node backend. Use a single service file to call Turalogin from your login endpoint. Works the same whether you are running Express, Fastify, Hapi, or a custom server.
If you can handle JSON over HTTP, you are done.
// Express example
import express from 'express';
const app = express();
app.post('/auth/start', async (req, res) => {
const { email } = req.body;
const response = await fetch('https://api.turalogin.com/auth/start', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TURALOGIN_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
const { sessionId } = await response.json();
res.json({ sessionId });
});
app.post('/auth/verify', async (req, res) => {
const { sessionId, code } = req.body;
const response = await fetch('https://api.turalogin.com/auth/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TURALOGIN_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ sessionId, code }),
});
const { token, user } = await response.json();
// Create your session
req.session.userId = user.id;
res.json({ success: true });
});Modern runtimes work out of the box. No SDK required. Fetch-based examples using native APIs. Clean, fast, and edge-friendly.
// Bun server example
const server = Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
if (url.pathname === '/auth/start' && req.method === 'POST') {
const { email } = await req.json();
const res = await fetch('https://api.turalogin.com/auth/start', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Bun.env.TURALOGIN_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
return new Response(JSON.stringify(await res.json()), {
headers: { 'Content-Type': 'application/json' },
});
}
return new Response('Not Found', { status: 404 });
},
});Simple server-to-server integration. One request to start auth. One request to exchange proof. Token verification happens locally using standard JWT libraries.
FastAPI examples are especially clean due to async support.
# FastAPI example
from fastapi import FastAPI, HTTPException
import httpx
import os
app = FastAPI()
TURALOGIN_API_KEY = os.environ["TURALOGIN_API_KEY"]
@app.post("/auth/start")
async def start_auth(email: str):
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.turalogin.com/auth/start",
headers={"Authorization": f"Bearer {TURALOGIN_API_KEY}"},
json={"email": email},
)
return response.json()
@app.post("/auth/verify")
async def verify_auth(session_id: str, code: str):
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.turalogin.com/auth/verify",
headers={"Authorization": f"Bearer {TURALOGIN_API_KEY}"},
json={"sessionId": session_id, "code": code},
)
data = response.json()
# Create your own session here
return {"success": True, "user": data["user"]}Fits naturally into controller actions or service objects. Rails apps can treat Turalogin like any external identity provider without OAuth overhead.
# Rails controller example
class AuthController < ApplicationController
def start
response = HTTParty.post(
"https://api.turalogin.com/auth/start",
headers: {
"Authorization" => "Bearer #{ENV['TURALOGIN_API_KEY']}",
"Content-Type" => "application/json"
},
body: { email: params[:email] }.to_json
)
render json: response.parsed_response
end
def verify
response = HTTParty.post(
"https://api.turalogin.com/auth/verify",
headers: {
"Authorization" => "Bearer #{ENV['TURALOGIN_API_KEY']}",
"Content-Type" => "application/json"
},
body: { sessionId: params[:session_id], code: params[:code] }.to_json
)
data = response.parsed_response
session[:user_id] = data["user"]["id"]
render json: { success: true }
end
endStrong fit for enterprise and internal tools. Use standard REST clients. Tokens integrate cleanly with Spring Security if desired, or you can manage sessions manually.
// Spring Boot controller
@RestController
@RequestMapping("/auth")
public class AuthController {
@Value("${turalogin.api-key}")
private String apiKey;
private final RestTemplate restTemplate = new RestTemplate();
@PostMapping("/start")
public ResponseEntity<?> startAuth(@RequestBody Map<String, String> body) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(apiKey);
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> request = new HttpEntity<>(
Map.of("email", body.get("email")), headers
);
return restTemplate.postForEntity(
"https://api.turalogin.com/auth/start",
request,
Map.class
);
}
@PostMapping("/verify")
public ResponseEntity<?> verifyAuth(@RequestBody Map<String, String> body) {
// Similar pattern - call Turalogin, then create your session
// ...
}
}Lightweight and explicit. Use net/http or any HTTP client. JWT verification is fast and simple. Ideal for APIs and microservices.
package main
import (
"bytes"
"encoding/json"
"net/http"
"os"
)
func startAuth(w http.ResponseWriter, r *http.Request) {
var body struct {
Email string `json:"email"`
}
json.NewDecoder(r.Body).Decode(&body)
payload, _ := json.Marshal(map[string]string{"email": body.Email})
req, _ := http.NewRequest("POST", "https://api.turalogin.com/auth/start", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+os.Getenv("TURALOGIN_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}Works well for traditional web apps and APIs. Turalogin slots into existing auth flows without replacing your user model or session handling.
<?php
// Laravel controller example
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class AuthController extends Controller
{
public function start(Request $request)
{
$response = Http::withToken(config('services.turalogin.key'))
->post('https://api.turalogin.com/auth/start', [
'email' => $request->email,
]);
return $response->json();
}
public function verify(Request $request)
{
$response = Http::withToken(config('services.turalogin.key'))
->post('https://api.turalogin.com/auth/verify', [
'sessionId' => $request->session_id,
'code' => $request->code,
]);
$data = $response->json();
// Create your session
auth()->loginUsingId($data['user']['id']);
return ['success' => true];
}
}First-class backend support. Use HttpClient for the exchange and standard middleware for session handling or token validation.
// ASP.NET Core controller
[ApiController]
[Route("auth")]
public class AuthController : ControllerBase
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
public AuthController(IConfiguration config)
{
_httpClient = new HttpClient();
_apiKey = config["Turalogin:ApiKey"];
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", _apiKey);
}
[HttpPost("start")]
public async Task<IActionResult> Start([FromBody] StartRequest request)
{
var response = await _httpClient.PostAsJsonAsync(
"https://api.turalogin.com/auth/start",
new { email = request.Email }
);
return Ok(await response.Content.ReadFromJsonAsync<object>());
}
[HttpPost("verify")]
public async Task<IActionResult> Verify([FromBody] VerifyRequest request)
{
var response = await _httpClient.PostAsJsonAsync(
"https://api.turalogin.com/auth/verify",
new { sessionId = request.SessionId, code = request.Code }
);
// Create your session and return
return Ok(new { success = true });
}
}Works with AWS Lambda, Vercel Functions, Cloudflare Workers, and similar environments. No long-lived connections. No shared state required. All calls are stateless and fast.
// Cloudflare Worker example
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === '/auth/start' && request.method === 'POST') {
const { email } = await request.json();
const response = await fetch('https://api.turalogin.com/auth/start', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.TURALOGIN_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
return new Response(JSON.stringify(await response.json()), {
headers: { 'Content-Type': 'application/json' },
});
}
return new Response('Not Found', { status: 404 });
},
};Every backend uses the same small surface area:
POST /auth/start → { sessionId } // Start authentication
POST /auth/verify → { token, user } // Exchange code for JWT
POST /auth/refresh → { token } // Refresh expired token (optional)Once you understand one example, you understand them all.
Auth should not dictate your stack.
Turalogin works the same whether you are shipping a Next.js SaaS, a Python API, an internal admin tool, or a dozen services at once.
You do not need to adopt a new auth model.
You do not need frontend rewrites.
You keep your users, sessions, and app logic.
Backend-first by design. Simple everywhere.