ASP.NET Core and enterprise applications
Turalogin provides first-class backend support for .NET applications. Start auth to send a magic link email, handle the callback when users click the link, and verify the token. Works seamlessly with ASP.NET Core's authentication system.
Configure your settings in appsettings.json for local development and production.
1 { 2 "Turalogin": { 3 "ApiKey": "your-api-key-here", 4 "ValidationUrl": "http://localhost:5000/auth/callback" 5 } 6 } 7 8 // For production, use environment variables or user secrets: 9 // dotnet user-secrets set "Turalogin:ApiKey" "tl_live_xxx" 10 // dotnet user-secrets set "Turalogin:ValidationUrl" "https://myapp.com/auth/callback"
Create a controller endpoint to initiate authentication. This sends a magic link to the user's email.
1 using Microsoft.AspNetCore.Mvc; 2 using System.Text.Json; 3 4 namespace MyApp.Controllers; 5 6 [ApiController] 7 [Route("auth")] 8 public class AuthController : ControllerBase 9 { 10 private readonly TuraloginService _turalogin; 11 12 public AuthController(TuraloginService turalogin) 13 { 14 _turalogin = turalogin; 15 } 16 17 [HttpPost("start")] 18 public async Task<IActionResult> Start([FromBody] StartAuthRequest request) 19 { 20 if (string.IsNullOrEmpty(request.Email)) 21 { 22 return BadRequest(new { error = "Email is required" }); 23 } 24 25 try 26 { 27 await _turalogin.StartAuthAsync(request.Email); 28 return Ok(new { 29 success = true, 30 message = "Check your email for the login link" 31 }); 32 } 33 catch (TuraloginException ex) 34 { 35 return StatusCode(ex.StatusCode, new { error = ex.Message }); 36 } 37 } 38 } 39 40 public record StartAuthRequest(string Email);
Create a callback endpoint that receives the token from the magic link and verifies it.
1 [HttpGet("callback")] 2 public async Task<IActionResult> Callback([FromQuery] string token) 3 { 4 if (string.IsNullOrEmpty(token)) 5 { 6 return Redirect("/login?error=invalid_link"); 7 } 8 9 try 10 { 11 var result = await _turalogin.VerifyTokenAsync(token); 12 13 // Create claims for the user 14 var claims = new List<Claim> 15 { 16 new Claim(ClaimTypes.NameIdentifier, result.User.Id), 17 new Claim(ClaimTypes.Email, result.User.Email), 18 }; 19 20 var claimsIdentity = new ClaimsIdentity( 21 claims, 22 CookieAuthenticationDefaults.AuthenticationScheme 23 ); 24 25 var authProperties = new AuthenticationProperties 26 { 27 IsPersistent = true, 28 ExpiresUtc = DateTimeOffset.UtcNow.AddDays(7), 29 }; 30 31 await HttpContext.SignInAsync( 32 CookieAuthenticationDefaults.AuthenticationScheme, 33 new ClaimsPrincipal(claimsIdentity), 34 authProperties 35 ); 36 37 return Redirect("/dashboard"); 38 } 39 catch (TuraloginException) 40 { 41 return Redirect("/login?error=verification_failed"); 42 } 43 } 44 45 [HttpPost("logout")] 46 [Authorize] 47 public async Task<IActionResult> Logout() 48 { 49 await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); 50 return Ok(new { success = true }); 51 }
A service class that handles all Turalogin API interactions.
1 using System.Net.Http.Headers; 2 using System.Text; 3 using System.Text.Json; 4 5 namespace MyApp.Services; 6 7 public class TuraloginService 8 { 9 private readonly HttpClient _httpClient; 10 private readonly string _apiKey; 11 private readonly string _validationUrl; 12 private const string BaseUrl = "https://api.turalogin.com/api/v1"; 13 14 public TuraloginService(HttpClient httpClient, IConfiguration config) 15 { 16 _httpClient = httpClient; 17 _apiKey = config["Turalogin:ApiKey"] 18 ?? throw new ArgumentNullException("Turalogin API key not configured"); 19 _validationUrl = config["Turalogin:ValidationUrl"] 20 ?? throw new ArgumentNullException("Turalogin validation URL not configured"); 21 22 _httpClient.DefaultRequestHeaders.Authorization = 23 new AuthenticationHeaderValue("Bearer", _apiKey); 24 } 25 26 public async Task StartAuthAsync(string email) 27 { 28 await PostAsync("/auth/start", new { 29 email, 30 validationUrl = _validationUrl // Where the magic link redirects to 31 }); 32 } 33 34 public async Task<AuthResult> VerifyTokenAsync(string token) 35 { 36 var response = await PostAsync("/auth/verify", new { sessionId = token }); 37 38 var userElement = response.GetProperty("user"); 39 return new AuthResult( 40 Token: response.GetProperty("token").GetString()!, 41 User: new TuraloginUser( 42 Id: userElement.GetProperty("id").GetString()!, 43 Email: userElement.GetProperty("email").GetString()! 44 ) 45 ); 46 } 47 48 private async Task<JsonElement> PostAsync(string endpoint, object data) 49 { 50 var json = JsonSerializer.Serialize(data); 51 var content = new StringContent(json, Encoding.UTF8, "application/json"); 52 53 var response = await _httpClient.PostAsync($"{BaseUrl}{endpoint}", content); 54 var responseBody = await response.Content.ReadAsStringAsync(); 55 var result = JsonDocument.Parse(responseBody).RootElement; 56 57 if (!response.IsSuccessStatusCode) 58 { 59 var error = result.TryGetProperty("error", out var e) 60 ? e.GetString() 61 : "Unknown error"; 62 var code = result.TryGetProperty("code", out var c) 63 ? c.GetString() 64 : "UNKNOWN"; 65 66 throw new TuraloginException(error!, code!, (int)response.StatusCode); 67 } 68 69 return result; 70 } 71 } 72 73 public record AuthResult(string Token, TuraloginUser User); 74 public record TuraloginUser(string Id, string Email);
Custom exception for Turalogin-related errors.
1 namespace MyApp.Exceptions; 2 3 public class TuraloginException : Exception 4 { 5 public string ErrorCode { get; } 6 public int StatusCode { get; } 7 8 public TuraloginException(string message, string code, int status) 9 : base(message) 10 { 11 ErrorCode = code; 12 StatusCode = status; 13 } 14 }
Middleware to handle Turalogin exceptions globally.
1 using System.Text.Json; 2 using MyApp.Exceptions; 3 4 namespace MyApp.Middleware; 5 6 public class TuraloginExceptionMiddleware 7 { 8 private readonly RequestDelegate _next; 9 private readonly ILogger<TuraloginExceptionMiddleware> _logger; 10 11 public TuraloginExceptionMiddleware( 12 RequestDelegate next, 13 ILogger<TuraloginExceptionMiddleware> logger) 14 { 15 _next = next; 16 _logger = logger; 17 } 18 19 public async Task InvokeAsync(HttpContext context) 20 { 21 try 22 { 23 await _next(context); 24 } 25 catch (TuraloginException ex) 26 { 27 _logger.LogWarning( 28 "Turalogin error: {Message} ({Code})", 29 ex.Message, 30 ex.ErrorCode 31 ); 32 33 var message = ex.ErrorCode switch 34 { 35 "INVALID_EMAIL" => "Please provide a valid email address.", 36 "SESSION_EXPIRED" => "Login link has expired. Please try again.", 37 "INVALID_TOKEN" => "Invalid login link.", 38 "RATE_LIMITED" => "Too many attempts. Please wait a moment.", 39 _ => "Authentication error. Please try again." 40 }; 41 42 context.Response.StatusCode = ex.StatusCode; 43 context.Response.ContentType = "application/json"; 44 45 await context.Response.WriteAsync(JsonSerializer.Serialize(new 46 { 47 error = message, 48 code = ex.ErrorCode 49 })); 50 } 51 } 52 } 53 54 // Extension method 55 public static class TuraloginExceptionMiddlewareExtensions 56 { 57 public static IApplicationBuilder UseTuraloginExceptionHandler( 58 this IApplicationBuilder builder) 59 { 60 return builder.UseMiddleware<TuraloginExceptionMiddleware>(); 61 } 62 }
Complete Program.cs setup with magic link authentication.
1 using Microsoft.AspNetCore.Authentication.Cookies; 2 using MyApp.Services; 3 using MyApp.Middleware; 4 5 var builder = WebApplication.CreateBuilder(args); 6 7 // Add services 8 builder.Services.AddControllers(); 9 builder.Services.AddHttpClient<TuraloginService>(); 10 11 // Configure authentication 12 builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) 13 .AddCookie(options => 14 { 15 options.Cookie.HttpOnly = true; 16 options.Cookie.SecurePolicy = CookieSecurePolicy.Always; 17 options.Cookie.SameSite = SameSiteMode.Lax; 18 options.ExpireTimeSpan = TimeSpan.FromDays(7); 19 options.SlidingExpiration = true; 20 options.Events.OnRedirectToLogin = context => 21 { 22 context.Response.StatusCode = StatusCodes.Status401Unauthorized; 23 return Task.CompletedTask; 24 }; 25 }); 26 27 builder.Services.AddAuthorization(); 28 29 // CORS (if needed) 30 builder.Services.AddCors(options => 31 { 32 options.AddDefaultPolicy(policy => 33 { 34 policy.WithOrigins(builder.Configuration["FrontendUrl"]!) 35 .AllowCredentials() 36 .AllowAnyMethod() 37 .AllowAnyHeader(); 38 }); 39 }); 40 41 var app = builder.Build(); 42 43 // Middleware pipeline 44 app.UseCors(); 45 app.UseTuraloginExceptionHandler(); 46 app.UseAuthentication(); 47 app.UseAuthorization(); 48 app.MapControllers(); 49 50 app.Run(); 51 52 // appsettings.json 53 /* 54 { 55 "Turalogin": { 56 "ApiKey": "your-api-key-here", 57 "ValidationUrl": "http://localhost:5000/auth/callback" 58 }, 59 "FrontendUrl": "http://localhost:3000" 60 } 61 */ 62 63 // Protected endpoint example 64 [ApiController] 65 [Route("api")] 66 [Authorize] 67 public class ApiController : ControllerBase 68 { 69 [HttpGet("me")] 70 public IActionResult GetMe() 71 { 72 return Ok(new 73 { 74 id = User.FindFirst(ClaimTypes.NameIdentifier)?.Value, 75 email = User.FindFirst(ClaimTypes.Email)?.Value 76 }); 77 } 78 79 [HttpGet("dashboard")] 80 public IActionResult Dashboard() 81 { 82 var email = User.FindFirst(ClaimTypes.Email)?.Value; 83 return Ok(new { message = $"Welcome to the dashboard, {email}!" }); 84 } 85 }