Laravel, Symfony, and vanilla PHP
Turalogin works well for traditional PHP web apps and APIs. Start auth to send a magic link email, handle the callback when users click the link, and verify the token. It slots into existing auth flows without replacing your user model or session handling.
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:8000/auth/callback 7 8 # Production (update for your domain): 9 # APP_LOGIN_VALIDATION_URL=https://myapp.com/auth/callback
Create a controller method to initiate authentication. This sends a magic link to the user's email.
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use Illuminate\Http\Request; 6 use Illuminate\Support\Facades\Http; 7 8 class AuthController extends Controller 9 { 10 private string $apiKey; 11 private string $apiUrl = 'https://api.turalogin.com/api/v1'; 12 private string $validationUrl; 13 14 public function __construct() 15 { 16 $this->apiKey = config('services.turalogin.key'); 17 $this->validationUrl = config('services.turalogin.validation_url'); 18 } 19 20 public function start(Request $request) 21 { 22 $request->validate([ 23 'email' => 'required|email', 24 ]); 25 26 $response = Http::withToken($this->apiKey) 27 ->post("{$this->apiUrl}/auth/start", [ 28 'email' => $request->email, 29 'validationUrl' => $this->validationUrl, // Where the magic link redirects to 30 ]); 31 32 if ($response->failed()) { 33 return response()->json( 34 $response->json(), 35 $response->status() 36 ); 37 } 38 39 return response()->json([ 40 'success' => true, 41 'message' => 'Check your email for the login link', 42 ]); 43 } 44 }
Create a callback method that receives the token from the magic link and verifies it.
1 public function callback(Request $request) 2 { 3 $token = $request->query('token'); 4 5 if (!$token) { 6 return redirect('/login')->with('error', 'Invalid login link'); 7 } 8 9 $response = Http::withToken($this->apiKey) 10 ->post("{$this->apiUrl}/auth/verify", [ 11 'sessionId' => $token, 12 ]); 13 14 if ($response->failed()) { 15 return redirect('/login')->with('error', 'Login link is invalid or expired'); 16 } 17 18 $data = $response->json(); 19 $userData = $data['user']; 20 21 // Find or create user 22 $user = User::firstOrCreate( 23 ['email' => $userData['email']], 24 ['turalogin_id' => $userData['id']] 25 ); 26 27 // Update last login 28 $user->update(['last_login_at' => now()]); 29 30 // Log the user in 31 auth()->login($user, true); 32 33 // Store Turalogin token if needed 34 session(['turalogin_token' => $data['token']]); 35 36 return redirect('/dashboard')->with('success', 'Successfully signed in!'); 37 } 38 39 public function logout(Request $request) 40 { 41 auth()->logout(); 42 $request->session()->invalidate(); 43 $request->session()->regenerateToken(); 44 45 return response()->json(['success' => true]); 46 }
A service class that encapsulates Turalogin API interactions.
1 <?php 2 3 namespace App\Services; 4 5 use Illuminate\Support\Facades\Http; 6 use App\Exceptions\TuraloginException; 7 8 class TuraloginService 9 { 10 private string $apiKey; 11 private string $validationUrl; 12 private string $baseUrl = 'https://api.turalogin.com/api/v1'; 13 14 public function __construct() 15 { 16 $this->apiKey = config('services.turalogin.key'); 17 $this->validationUrl = config('services.turalogin.validation_url'); 18 } 19 20 public function startAuth(string $email): void 21 { 22 $this->request('/auth/start', [ 23 'email' => $email, 24 'validationUrl' => $this->validationUrl, 25 ]); 26 } 27 28 public function verifyToken(string $token): array 29 { 30 return $this->request('/auth/verify', [ 31 'sessionId' => $token, 32 ]); 33 } 34 35 private function request(string $endpoint, array $data): array 36 { 37 $response = Http::withToken($this->apiKey) 38 ->post("{$this->baseUrl}{$endpoint}", $data); 39 40 if ($response->failed()) { 41 $error = $response->json(); 42 throw new TuraloginException( 43 $error['error'] ?? 'Unknown error', 44 $error['code'] ?? 'UNKNOWN', 45 $response->status() 46 ); 47 } 48 49 return $response->json(); 50 } 51 } 52 53 // app/Exceptions/TuraloginException.php 54 namespace App\Exceptions; 55 56 use Exception; 57 58 class TuraloginException extends Exception 59 { 60 public string $errorCode; 61 public int $statusCode; 62 63 public function __construct(string $message, string $code, int $status) 64 { 65 parent::__construct($message); 66 $this->errorCode = $code; 67 $this->statusCode = $status; 68 } 69 }
Add Turalogin configuration to your services config file.
1 <?php 2 3 return [ 4 // ... other services 5 6 'turalogin' => [ 7 'key' => env('TURALOGIN_API_KEY'), 8 'validation_url' => env('APP_LOGIN_VALIDATION_URL'), 9 ], 10 ];
For projects not using a framework, use cURL directly.
1 <?php 2 3 session_start(); 4 5 define('TURALOGIN_API_KEY', getenv('TURALOGIN_API_KEY')); 6 define('TURALOGIN_API_URL', 'https://api.turalogin.com/api/v1'); 7 define('VALIDATION_URL', getenv('APP_LOGIN_VALIDATION_URL')); 8 9 function turaloginRequest(string $endpoint, array $data): array 10 { 11 $ch = curl_init(TURALOGIN_API_URL . $endpoint); 12 13 curl_setopt_array($ch, [ 14 CURLOPT_RETURNTRANSFER => true, 15 CURLOPT_POST => true, 16 CURLOPT_POSTFIELDS => json_encode($data), 17 CURLOPT_HTTPHEADER => [ 18 'Authorization: Bearer ' . TURALOGIN_API_KEY, 19 'Content-Type: application/json', 20 ], 21 ]); 22 23 $response = curl_exec($ch); 24 $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 25 curl_close($ch); 26 27 $result = json_decode($response, true); 28 29 if ($statusCode !== 200) { 30 throw new Exception($result['error'] ?? 'Unknown error', $statusCode); 31 } 32 33 return $result; 34 } 35 36 // Handle start auth 37 if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_GET['action'] === 'start') { 38 $input = json_decode(file_get_contents('php://input'), true); 39 40 try { 41 turaloginRequest('/auth/start', [ 42 'email' => $input['email'], 43 'validationUrl' => VALIDATION_URL, 44 ]); 45 echo json_encode(['success' => true, 'message' => 'Check your email']); 46 } catch (Exception $e) { 47 http_response_code($e->getCode()); 48 echo json_encode(['error' => $e->getMessage()]); 49 } 50 exit; 51 } 52 53 // Handle callback 54 if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_GET['action'] === 'callback') { 55 $token = $_GET['token'] ?? null; 56 57 if (!$token) { 58 header('Location: /login?error=invalid_link'); 59 exit; 60 } 61 62 try { 63 $result = turaloginRequest('/auth/verify', ['sessionId' => $token]); 64 65 // Create session 66 $_SESSION['user_id'] = $result['user']['id']; 67 $_SESSION['email'] = $result['user']['email']; 68 69 header('Location: /dashboard'); 70 } catch (Exception $e) { 71 header('Location: /login?error=verification_failed'); 72 } 73 exit; 74 }
A complete Laravel controller with magic link authentication and dependency injection.
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use App\Models\User; 6 use App\Services\TuraloginService; 7 use App\Exceptions\TuraloginException; 8 use Illuminate\Http\Request; 9 use Illuminate\Http\JsonResponse; 10 use Illuminate\Http\RedirectResponse; 11 12 class AuthController extends Controller 13 { 14 public function __construct( 15 private TuraloginService $turalogin 16 ) {} 17 18 public function start(Request $request): JsonResponse 19 { 20 $request->validate(['email' => 'required|email']); 21 22 try { 23 $this->turalogin->startAuth($request->email); 24 return response()->json([ 25 'success' => true, 26 'message' => 'Check your email for the login link', 27 ]); 28 } catch (TuraloginException $e) { 29 return response()->json( 30 ['error' => $e->getMessage()], 31 $e->statusCode 32 ); 33 } 34 } 35 36 public function callback(Request $request): RedirectResponse 37 { 38 $token = $request->query('token'); 39 40 if (!$token) { 41 return redirect('/login')->with('error', 'Invalid login link'); 42 } 43 44 try { 45 $result = $this->turalogin->verifyToken($token); 46 47 // Find or create user 48 $user = User::firstOrCreate( 49 ['email' => $result['user']['email']], 50 [ 51 'turalogin_id' => $result['user']['id'], 52 'name' => $result['user']['email'], 53 ] 54 ); 55 56 $user->update(['last_login_at' => now()]); 57 58 // Log in 59 auth()->login($user, true); 60 session(['turalogin_token' => $result['token']]); 61 62 return redirect('/dashboard')->with('success', 'Successfully signed in!'); 63 64 } catch (TuraloginException $e) { 65 return redirect('/login')->with('error', 'Login link is invalid or expired'); 66 } 67 } 68 69 public function logout(Request $request): JsonResponse 70 { 71 auth()->logout(); 72 $request->session()->invalidate(); 73 $request->session()->regenerateToken(); 74 75 return response()->json(['success' => true]); 76 } 77 78 public function me(Request $request): JsonResponse 79 { 80 return response()->json([ 81 'user' => $request->user()->only(['id', 'email', 'name']), 82 ]); 83 } 84 } 85 86 // routes/web.php 87 use App\Http\Controllers\AuthController; 88 89 Route::post('/auth/start', [AuthController::class, 'start']); 90 Route::get('/auth/callback', [AuthController::class, 'callback']); 91 Route::post('/auth/logout', [AuthController::class, 'logout'])->middleware('auth'); 92 Route::get('/auth/me', [AuthController::class, 'me'])->middleware('auth');