$ cat /posts/supabase-magic-link-passwordless-authentication-guide.md
[tags]Supabase

Supabase Magic Link: Passwordless Authentication Guide

drwxr-xr-x2026-01-255 min0 views
Supabase Magic Link: Passwordless Authentication Guide

Magic link authentication provides a modern, passwordless login experience where users receive a one-time secure link via email instead of managing passwords, eliminating password-related security risks, reducing support requests for forgotten credentials, and improving conversion rates by simplifying the signup process. This comprehensive guide covers implementing magic link authentication with Supabase, building React components for email collection and verification, handling authentication callbacks, managing user sessions after magic link login, customizing email templates, implementing rate limiting, and combining magic links with traditional authentication methods. Magic links are increasingly popular for consumer applications, internal tools, and SaaS products where user convenience and security are paramount. Unlike passwords that can be weak, reused, or phished, magic links provide time-limited, single-use authentication tokens sent directly to verified email addresses. Before proceeding, understand authentication fundamentals and email authentication basics.

Why Magic Links?

BenefitDescriptionUser ImpactSecurity Impact
No PasswordsEliminates password creation/storageFaster signupNo password breaches
Better UXOne-click login from emailFewer stepsReduces phishing risk
Lower SupportNo forgotten password requestsLess frictionN/A
Higher ConversionSimpler signup processMore signupsN/A
Email VerificationAutomatic email validationSeamlessVerifies email ownership

How Magic Links Work

  1. User enters email address in your application
  2. Supabase sends secure, time-limited link to the email
  3. User clicks link in their email inbox
  4. User is redirected to your app and automatically logged in
  5. Session is created and stored in browser
  6. User can access protected resources

Basic Implementation

javascriptMagicLinkLogin.jsx
// MagicLinkLogin.jsx
import { useState } from 'react'
import { supabase } from './supabaseClient'

function MagicLinkLogin() {
  const [email, setEmail] = useState('')
  const [loading, setLoading] = useState(false)
  const [sent, setSent] = useState(false)
  const [error, setError] = useState(null)

  async function handleMagicLink(e) {
    e.preventDefault()
    setLoading(true)
    setError(null)

    const { data, error } = await supabase.auth.signInWithOtp({
      email: email,
      options: {
        // URL to redirect after clicking magic link
        emailRedirectTo: `${window.location.origin}/dashboard`
      }
    })

    setLoading(false)

    if (error) {
      setError(error.message)
      return
    }

    setSent(true)
  }

  if (sent) {
    return (
      <div className="magic-link-sent">
        <h2>Check Your Email</h2>
        <p>We sent a magic link to <strong>{email}</strong></p>
        <p>Click the link in the email to log in.</p>
        <button onClick={() => setSent(false)}>Send Another Link</button>
      </div>
    )
  }

  return (
    <div className="magic-link-form">
      <h2>Log In with Magic Link</h2>
      <p>Enter your email to receive a login link</p>
      <form onSubmit={handleMagicLink}>
        <input
          type="email"
          placeholder="[email protected]"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        {error && <p className="error">{error}</p>}
        <button type="submit" disabled={loading}>
          {loading ? 'Sending...' : 'Send Magic Link'}
        </button>
      </form>
    </div>
  )
}

export default MagicLinkLogin

Handling Authentication Callback

javascriptAuthCallback.jsx
// App.jsx - Handle magic link callback
import { useEffect } from 'react'
import { supabase } from './supabaseClient'
import { useNavigate } from 'react-router-dom'

function App() {
  const navigate = useNavigate()

  useEffect(() => {
    // Listen for auth state changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        if (event === 'SIGNED_IN') {
          console.log('User signed in:', session.user.email)
          // Redirect to dashboard after successful login
          navigate('/dashboard')
        }
        
        if (event === 'TOKEN_REFRESHED') {
          console.log('Token refreshed')
        }
      }
    )

    return () => subscription.unsubscribe()
  }, [])

  return (
    // Your app routes
  )
}

// Alternative: Check for existing session on mount
function Dashboard() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    checkUser()
  }, [])

  async function checkUser() {
    const { data: { user } } = await supabase.auth.getUser()
    setUser(user)
    setLoading(false)
  }

  if (loading) return <div>Loading...</div>
  if (!user) return <div>Please log in</div>

  return (
    <div>
      <h1>Welcome {user.email}</h1>
      {/* Dashboard content */}
    </div>
  )
}

Combining with Password Auth

javascriptLoginPage.jsx
// LoginPage.jsx - Offer both options
import { useState } from 'react'
import { supabase } from './supabaseClient'

function LoginPage() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [useMagicLink, setUseMagicLink] = useState(false)
  const [loading, setLoading] = useState(false)
  const [message, setMessage] = useState(null)

  async function handleSubmit(e) {
    e.preventDefault()
    setLoading(true)
    setMessage(null)

    if (useMagicLink) {
      // Magic link login
      const { error } = await supabase.auth.signInWithOtp({ email })
      
      if (error) {
        setMessage({ type: 'error', text: error.message })
      } else {
        setMessage({ 
          type: 'success', 
          text: 'Check your email for the login link!' 
        })
      }
    } else {
      // Password login
      const { error } = await supabase.auth.signInWithPassword({
        email,
        password
      })
      
      if (error) {
        setMessage({ type: 'error', text: error.message })
      }
    }

    setLoading(false)
  }

  return (
    <div>
      <h2>Log In</h2>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="Email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        
        {!useMagicLink && (
          <input
            type="password"
            placeholder="Password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            required
          />
        )}

        {message && <p className={message.type}>{message.text}</p>}

        <button type="submit" disabled={loading}>
          {loading ? 'Please wait...' : 
           useMagicLink ? 'Send Magic Link' : 'Log In'}
        </button>
      </form>

      <button 
        type="button"
        onClick={() => setUseMagicLink(!useMagicLink)}
        className="toggle-method"
      >
        {useMagicLink ? 
          'Use password instead' : 
          'Use magic link instead'
        }
      </button>
    </div>
  )
}

export default LoginPage

Customizing Email Templates

Customize magic link emails in your Supabase Dashboard under Authentication > Email Templates. Modify the subject line, email body, and branding to match your application. Use variables like {{ .ConfirmationURL }} for the magic link and {{ .SiteURL }} for your app URL. Add your logo, brand colors, and custom messaging to improve trust and click-through rates.

Security Considerations

  • Link Expiration: Magic links expire after a set time (default 1 hour) preventing old links from working
  • Single Use: Each magic link can only be used once for security
  • Rate Limiting: Supabase limits magic link requests to prevent spam and abuse
  • HTTPS Required: Always use HTTPS to prevent link interception
  • Email Security: Magic links are only as secure as the user's email account
  • Add RLS Policies: Always implement Row Level Security to protect user data

Best Practices

  • Clear Instructions: Tell users to check their email and spam folder
  • Show Success Message: Confirm the email was sent with the exact email address
  • Allow Resending: Let users request another link if needed
  • Provide Alternative: Offer password login as backup option
  • Customize Emails: Brand your magic link emails for trust and recognition
  • Handle Errors Gracefully: Show helpful messages for invalid emails or rate limits
Pro Tip: Magic links work great for internal tools, admin panels, and apps where users value convenience over speed. For public-facing apps, consider offering both magic links and password authentication to accommodate different user preferences.

Common Issues

  • Email Not Received: Check spam folder, verify email provider settings, confirm SMTP configuration in Supabase
  • Link Expired: Request a new magic link; default expiration is 1 hour
  • Redirect Not Working: Verify emailRedirectTo URL matches your site URL in Supabase settings
  • Rate Limit Errors: Wait before requesting another link; default is 4 emails per hour per email address

Next Steps

  1. Add OAuth: Implement social login with Google and GitHub for more options
  2. Secure Data: Implement Row Level Security policies to protect user information
  3. Build Profiles: Create user profile pages with file uploads for avatars
  4. Production Deployment: Integrate with Next.js for server-side rendering

Conclusion

Magic link authentication offers a modern, passwordless login experience that improves user experience while maintaining strong security through time-limited, single-use tokens sent to verified email addresses. By eliminating passwords, you reduce support burden, improve conversion rates, and provide a seamless authentication flow that users appreciate. Supabase makes magic link implementation straightforward with built-in support, customizable email templates, and automatic session management. Whether used exclusively or combined with traditional password authentication, magic links provide flexibility in how users access your application. Always remember to customize email templates for branding, implement proper error handling, and protect user data with Row Level Security policies. With magic link authentication mastered, continue building secure applications with OAuth integration and comprehensive security policies.

$ cat /comments/ (0)

new_comment.sh

// Email hidden from public

>_

$ cat /comments/

// No comments found. Be the first!

[session] guest@{codershandbook}[timestamp] 2026

Navigation

Categories

Connect

Subscribe

// 2026 {Coders Handbook}. EOF.