Spring Boot and enterprise applications
Turalogin provides a strong fit for enterprise and internal tools built with Java. Start auth to send a magic link email, handle the callback when users click the link, and verify the token. Tokens work cleanly with Spring Security if desired.
Configure your environment variables in application.properties or application.yml.
1 # Your Turalogin API key from the dashboard 2 turalogin.api-key=${TURALOGIN_API_KEY} 3 4 # The URL where magic links will redirect to 5 # Development: 6 turalogin.validation-url=http://localhost:8080/auth/callback 7 8 # Production (update for your domain): 9 # turalogin.validation-url=https://myapp.com/auth/callback
Create a REST controller endpoint to initiate authentication. This sends a magic link to the user's email.
1 @RestController 2 @RequestMapping("/auth") 3 public class AuthController { 4 5 private final TuraloginService turaloginService; 6 7 public AuthController(TuraloginService turaloginService) { 8 this.turaloginService = turaloginService; 9 } 10 11 @PostMapping("/start") 12 public ResponseEntity<Map<String, Object>> startAuth( 13 @RequestBody StartAuthRequest request) { 14 try { 15 turaloginService.startAuth(request.getEmail()); 16 return ResponseEntity.ok(Map.of( 17 "success", true, 18 "message", "Check your email for the login link" 19 )); 20 } catch (TuraloginException e) { 21 return ResponseEntity 22 .status(e.getStatus()) 23 .body(Map.of("error", e.getMessage())); 24 } 25 } 26 } 27 28 // Request DTO 29 public record StartAuthRequest(String email) {}
Create a callback endpoint that receives the token from the magic link and verifies it.
1 @GetMapping("/callback") 2 public ResponseEntity<?> authCallback( 3 @RequestParam String token, 4 HttpSession session, 5 HttpServletResponse response) { 6 try { 7 AuthResult result = turaloginService.verifyToken(token); 8 9 // Store user in session 10 session.setAttribute("userId", result.user().id()); 11 session.setAttribute("email", result.user().email()); 12 session.setAttribute("turaloginToken", result.token()); 13 14 // Redirect to dashboard 15 response.sendRedirect("/dashboard"); 16 return null; 17 18 } catch (TuraloginException e) { 19 try { 20 response.sendRedirect("/login?error=verification_failed"); 21 } catch (IOException ex) { 22 // Handle redirect error 23 } 24 return null; 25 } catch (IOException e) { 26 return ResponseEntity.status(500).body(Map.of("error", "Redirect failed")); 27 } 28 } 29 30 @PostMapping("/logout") 31 public ResponseEntity<?> logout(HttpSession session) { 32 session.invalidate(); 33 return ResponseEntity.ok(Map.of("success", true)); 34 }
A service class that encapsulates all Turalogin API interactions.
1 @Service 2 public class TuraloginService { 3 4 private final RestTemplate restTemplate; 5 private final String apiKey; 6 private final String validationUrl; 7 private final String baseUrl = "https://api.turalogin.com/api/v1"; 8 9 public TuraloginService( 10 RestTemplateBuilder restTemplateBuilder, 11 @Value("${turalogin.api-key}") String apiKey, 12 @Value("${turalogin.validation-url}") String validationUrl) { 13 this.restTemplate = restTemplateBuilder.build(); 14 this.apiKey = apiKey; 15 this.validationUrl = validationUrl; 16 } 17 18 public void startAuth(String email) { 19 HttpHeaders headers = createHeaders(); 20 21 Map<String, String> body = Map.of( 22 "email", email, 23 "validationUrl", validationUrl // Where the magic link redirects to 24 ); 25 HttpEntity<Map<String, String>> request = new HttpEntity<>(body, headers); 26 27 try { 28 restTemplate.postForEntity( 29 baseUrl + "/auth/start", 30 request, 31 Map.class 32 ); 33 } catch (HttpClientErrorException e) { 34 throw handleError(e); 35 } 36 } 37 38 public AuthResult verifyToken(String token) { 39 HttpHeaders headers = createHeaders(); 40 41 Map<String, String> body = Map.of("sessionId", token); 42 HttpEntity<Map<String, String>> request = new HttpEntity<>(body, headers); 43 44 try { 45 ResponseEntity<AuthResponse> response = restTemplate.postForEntity( 46 baseUrl + "/auth/verify", 47 request, 48 AuthResponse.class 49 ); 50 AuthResponse authResponse = response.getBody(); 51 return new AuthResult( 52 authResponse.token(), 53 new User( 54 authResponse.user().get("id"), 55 authResponse.user().get("email") 56 ) 57 ); 58 } catch (HttpClientErrorException e) { 59 throw handleError(e); 60 } 61 } 62 63 private HttpHeaders createHeaders() { 64 HttpHeaders headers = new HttpHeaders(); 65 headers.setBearerAuth(apiKey); 66 headers.setContentType(MediaType.APPLICATION_JSON); 67 return headers; 68 } 69 70 private TuraloginException handleError(HttpClientErrorException e) { 71 return new TuraloginException( 72 e.getResponseBodyAsString(), 73 e.getStatusCode().value() 74 ); 75 } 76 }
A filter to protect routes and validate sessions.
1 @Component 2 public class AuthenticationFilter extends OncePerRequestFilter { 3 4 private final List<String> publicPaths = List.of( 5 "/auth/start", 6 "/auth/callback", 7 "/public" 8 ); 9 10 @Override 11 protected void doFilterInternal( 12 HttpServletRequest request, 13 HttpServletResponse response, 14 FilterChain filterChain) throws ServletException, IOException { 15 16 String path = request.getRequestURI(); 17 18 // Skip public paths 19 if (publicPaths.stream().anyMatch(path::startsWith)) { 20 filterChain.doFilter(request, response); 21 return; 22 } 23 24 // Check for valid session 25 HttpSession session = request.getSession(false); 26 if (session == null || session.getAttribute("userId") == null) { 27 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 28 response.setContentType(MediaType.APPLICATION_JSON_VALUE); 29 response.getWriter().write( 30 "{\"error\": \"Authentication required\"}" 31 ); 32 return; 33 } 34 35 filterChain.doFilter(request, response); 36 } 37 }
Global exception handler for Turalogin-related errors.
1 @RestControllerAdvice 2 public class GlobalExceptionHandler { 3 4 @ExceptionHandler(TuraloginException.class) 5 public ResponseEntity<Map<String, String>> handleTuraloginException( 6 TuraloginException e) { 7 8 String message = switch (e.getCode()) { 9 case "INVALID_EMAIL" -> "Please provide a valid email address"; 10 case "SESSION_EXPIRED" -> "Login link has expired. Please try again."; 11 case "INVALID_TOKEN" -> "Invalid login link"; 12 case "RATE_LIMITED" -> "Too many attempts. Please wait a moment."; 13 default -> "Authentication error. Please try again."; 14 }; 15 16 return ResponseEntity 17 .status(e.getStatus()) 18 .body(Map.of("error", message, "code", e.getCode())); 19 } 20 } 21 22 // Exception class 23 public class TuraloginException extends RuntimeException { 24 private final String code; 25 private final int status; 26 27 public TuraloginException(String message, String code, int status) { 28 super(message); 29 this.code = code; 30 this.status = status; 31 } 32 33 public String getCode() { return code; } 34 public int getStatus() { return status; } 35 }
A complete Spring Boot application configuration with magic link authentication.
1 @SpringBootApplication 2 public class Application { 3 public static void main(String[] args) { 4 SpringApplication.run(Application.class, args); 5 } 6 } 7 8 // application.yml 9 /* 10 turalogin: 11 api-key: ${TURALOGIN_API_KEY} 12 validation-url: ${APP_LOGIN_VALIDATION_URL:http://localhost:8080/auth/callback} 13 14 server: 15 servlet: 16 session: 17 timeout: 7d 18 cookie: 19 http-only: true 20 secure: true 21 same-site: lax 22 */ 23 24 // Protected Controller Example 25 @RestController 26 @RequestMapping("/api") 27 public class ApiController { 28 29 @GetMapping("/me") 30 public ResponseEntity<?> getCurrentUser(HttpSession session) { 31 String userId = (String) session.getAttribute("userId"); 32 String email = (String) session.getAttribute("email"); 33 34 return ResponseEntity.ok(Map.of( 35 "id", userId, 36 "email", email 37 )); 38 } 39 40 @GetMapping("/dashboard") 41 public ResponseEntity<?> getDashboard(HttpSession session) { 42 String email = (String) session.getAttribute("email"); 43 44 return ResponseEntity.ok(Map.of( 45 "message", "Welcome to the dashboard, " + email + "!" 46 )); 47 } 48 } 49 50 // Security Configuration (if using Spring Security) 51 @Configuration 52 @EnableWebSecurity 53 public class SecurityConfig { 54 55 @Bean 56 public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 57 http 58 .csrf(csrf -> csrf.disable()) 59 .sessionManagement(session -> 60 session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) 61 .authorizeHttpRequests(auth -> auth 62 .requestMatchers("/auth/**").permitAll() 63 .requestMatchers("/public/**").permitAll() 64 .anyRequest().authenticated() 65 ); 66 67 return http.build(); 68 } 69 }