Rails, Sinatra, and more
Turalogin fits naturally into Ruby controller actions or service objects. Start auth to send a magic link email, handle the callback when users click the link, and verify the token. Clean, idiomatic Ruby code that works with your existing authentication patterns.
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 controller action to initiate authentication. This sends a magic link to the user's email.
1 class AuthController < ApplicationController 2 skip_before_action :authenticate_user!, only: [:start, :callback] 3 4 def start 5 email = params[:email] 6 7 unless email.present? 8 return render json: { error: "Email is required" }, status: :bad_request 9 end 10 11 response = HTTParty.post( 12 "https://api.turalogin.com/api/v1/auth/start", 13 headers: { 14 "Authorization" => "Bearer #{ENV['TURALOGIN_API_KEY']}", 15 "Content-Type" => "application/json" 16 }, 17 body: { 18 email: email, 19 validationUrl: ENV['APP_LOGIN_VALIDATION_URL'] # Where the magic link redirects to 20 }.to_json 21 ) 22 23 if response.success? 24 render json: { success: true, message: "Check your email for the login link" } 25 else 26 render json: response.parsed_response, status: response.code 27 end 28 end 29 end
Create a callback action that receives the token from the magic link and verifies it.
1 def callback 2 token = params[:token] 3 4 unless token.present? 5 return redirect_to login_path, alert: "Invalid login link" 6 end 7 8 response = HTTParty.post( 9 "https://api.turalogin.com/api/v1/auth/verify", 10 headers: { 11 "Authorization" => "Bearer #{ENV['TURALOGIN_API_KEY']}", 12 "Content-Type" => "application/json" 13 }, 14 body: { sessionId: token }.to_json 15 ) 16 17 if response.success? 18 data = response.parsed_response 19 user_data = data["user"] 20 21 # Find or create user in your database 22 user = User.find_or_create_by(email: user_data["email"]) do |u| 23 u.turalogin_id = user_data["id"] 24 end 25 26 # Create session 27 session[:user_id] = user.id 28 session[:turalogin_token] = data["token"] 29 30 redirect_to dashboard_path, notice: "Successfully signed in!" 31 else 32 redirect_to login_path, alert: "Login link is invalid or expired" 33 end 34 end 35 36 def logout 37 reset_session 38 render json: { success: true } 39 end
Extract Turalogin API calls into a service object for cleaner controllers.
1 class TuraloginService 2 BASE_URL = "https://api.turalogin.com/api/v1" 3 4 class Error < StandardError 5 attr_reader :code, :status 6 7 def initialize(message, code:, status:) 8 super(message) 9 @code = code 10 @status = status 11 end 12 end 13 14 def initialize 15 @api_key = ENV.fetch("TURALOGIN_API_KEY") 16 @validation_url = ENV.fetch("APP_LOGIN_VALIDATION_URL") 17 end 18 19 def start_auth(email) 20 request("/auth/start", { 21 email: email, 22 validationUrl: @validation_url 23 }) 24 end 25 26 def verify_token(token) 27 request("/auth/verify", { sessionId: token }) 28 end 29 30 private 31 32 def request(endpoint, body) 33 response = HTTParty.post( 34 "#{BASE_URL}#{endpoint}", 35 headers: { 36 "Authorization" => "Bearer #{@api_key}", 37 "Content-Type" => "application/json" 38 }, 39 body: body.to_json 40 ) 41 42 unless response.success? 43 data = response.parsed_response 44 raise Error.new( 45 data["error"] || "Unknown error", 46 code: data["code"] || "UNKNOWN", 47 status: response.code 48 ) 49 end 50 51 response.parsed_response 52 end 53 end 54 55 # Usage in controller: 56 # @turalogin = TuraloginService.new 57 # @turalogin.start_auth(params[:email])
Create a concern to handle authentication across controllers.
1 module TuraloginAuth 2 extend ActiveSupport::Concern 3 4 included do 5 before_action :authenticate_user! 6 helper_method :current_user, :user_signed_in? 7 end 8 9 def current_user 10 return @current_user if defined?(@current_user) 11 12 if session[:user_id] 13 @current_user = User.find_by(id: session[:user_id]) 14 end 15 end 16 17 def user_signed_in? 18 current_user.present? 19 end 20 21 def authenticate_user! 22 unless user_signed_in? 23 respond_to do |format| 24 format.html { redirect_to login_path } 25 format.json { 26 render json: { error: "Authentication required" }, status: :unauthorized 27 } 28 end 29 end 30 end 31 32 def sign_in(user) 33 session[:user_id] = user.id 34 @current_user = user 35 end 36 37 def sign_out 38 reset_session 39 @current_user = nil 40 end 41 end 42 43 # Include in ApplicationController: 44 # class ApplicationController < ActionController::Base 45 # include TuraloginAuth 46 # end
Handle Turalogin-specific errors in your controllers.
1 module TuraloginErrorHandling 2 extend ActiveSupport::Concern 3 4 included do 5 rescue_from TuraloginService::Error, with: :handle_turalogin_error 6 end 7 8 private 9 10 def handle_turalogin_error(error) 11 case error.code 12 when "INVALID_EMAIL" 13 render json: { 14 error: "Please provide a valid email address" 15 }, status: :bad_request 16 17 when "SESSION_EXPIRED" 18 render json: { 19 error: "Login link has expired. Please try again." 20 }, status: :bad_request 21 22 when "INVALID_TOKEN" 23 render json: { 24 error: "Invalid login link" 25 }, status: :bad_request 26 27 when "RATE_LIMITED" 28 render json: { 29 error: "Too many attempts. Please wait a moment." 30 }, status: :too_many_requests 31 32 else 33 Rails.logger.error("Turalogin error: #{error.message} (#{error.code})") 34 render json: { 35 error: "Authentication error. Please try again." 36 }, status: error.status || :internal_server_error 37 end 38 end 39 end
A complete Rails controller with magic link authentication and proper error handling.
1 class AuthController < ApplicationController 2 include TuraloginErrorHandling 3 4 skip_before_action :authenticate_user!, only: [:start, :callback] 5 before_action :redirect_if_authenticated, only: [:start, :callback] 6 7 def start 8 email = params.require(:email) 9 10 turalogin.start_auth(email) 11 12 render json: { success: true, message: "Check your email for the login link" } 13 end 14 15 def callback 16 token = params[:token] 17 18 unless token.present? 19 return redirect_to login_path, alert: "Invalid login link" 20 end 21 22 result = turalogin.verify_token(token) 23 user_data = result["user"] 24 25 # Find or create user 26 user = User.find_or_create_by!(email: user_data["email"]) do |u| 27 u.turalogin_id = user_data["id"] 28 end 29 30 # Update last sign in 31 user.update!(last_sign_in_at: Time.current) 32 33 # Create session 34 sign_in(user) 35 session[:turalogin_token] = result["token"] 36 37 redirect_to dashboard_path, notice: "Successfully signed in!" 38 rescue TuraloginService::Error => e 39 redirect_to login_path, alert: "Login link is invalid or expired" 40 end 41 42 def logout 43 sign_out 44 render json: { success: true } 45 end 46 47 def me 48 render json: { 49 user: current_user.as_json(only: [:id, :email, :created_at]) 50 } 51 end 52 53 private 54 55 def turalogin 56 @turalogin ||= TuraloginService.new 57 end 58 59 def redirect_if_authenticated 60 if user_signed_in? 61 respond_to do |format| 62 format.html { redirect_to dashboard_path } 63 format.json { 64 render json: { error: "Already authenticated" }, status: :forbidden 65 } 66 end 67 end 68 end 69 end 70 71 # Routes (config/routes.rb): 72 # Rails.application.routes.draw do 73 # post 'auth/start', to: 'auth#start' 74 # get 'auth/callback', to: 'auth#callback' 75 # delete 'auth/logout', to: 'auth#logout' 76 # get 'auth/me', to: 'auth#me' 77 # end