Login with Magic Link
Supabase supports login with Magic Link. This is a secure way to authenticate users without requiring them to remember a password.
Server action
This server action below is used to login with Magic Link.
'use server';
import { createSupabaseUserServerActionClient } from '@/supabase-clients/user/createSupabaseUserServerActionClient';
import { AuthProvider } from '@/types';
import { toSiteURL } from '@/utils/helpers';
export const signInWithMagicLink = async (email: string, next?: string) => {
const supabase = createSupabaseUserServerActionClient();
const redirectUrl = new URL(toSiteURL('/auth/callback'));
if (next) {
redirectUrl.searchParams.set('next', next);
}
const { error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: redirectUrl.toString(),
},
});
if (error) {
console.log(error);
throw error;
}
};
Usage
'use client';
import { Email } from '@/components/presentational/tailwind/Auth/Email';
import { EmailAndPassword } from '@/components/presentational/tailwind/Auth/EmailAndPassword';
import { RenderProviders } from '@/components/presentational/tailwind/Auth/RenderProviders';
import {
signInWithMagicLink,
signInWithPassword,
signInWithProvider,
} from '@/data/auth/auth';
import { useToastMutation } from '@/hooks/useToastMutation';
import { AuthProvider } from '@/types';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
export function Login({
next,
nextActionType,
}: {
next?: string;
nextActionType?: string;
}) {
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const router = useRouter();
function redirectToDashboard() {
router.refresh();
if (next) {
router.push(`/auth/callback?next=${next}`);
} else {
router.push('/auth/callback');
}
}
const magicLinkMutation = useToastMutation(
async (email: string) => {
return await signInWithMagicLink(email, next);
},
{
loadingMessage: 'Sending magic link...',
errorMessage: 'Failed to send magic link',
successMessage: 'Magic link sent!',
onSuccess: () => {
setSuccessMessage('A magic link has been sent to your email!');
},
onError: (error) => {
console.log(error);
},
onMutate: () => {
setSuccessMessage(null);
},
},
);
const passwordMutation = useToastMutation(
async ({ email, password }: { email: string; password: string }) => {
return await signInWithPassword(email, password);
},
{
onSuccess: redirectToDashboard,
loadingMessage: 'Logging in...',
errorMessage: 'Failed to login',
successMessage: 'Logged in!',
},
);
const providerMutation = useToastMutation(
async (provider: AuthProvider) => {
return signInWithProvider(provider, next);
},
{
loadingMessage: 'Requesting login...',
successMessage: 'Redirecting...',
errorMessage: 'Failed to login',
},
);
return (
<div className="container h-full grid items-center text-left max-w-lg mx-auto overflow-auto">
{successMessage ? (
<p className="text-blue-500 text-sm">{successMessage}</p>
) : (
<div className="space-y-8 ">
<div className="flex flex-col items-start gap-0 w-[320px]">
<h1 className="text-xl font-[700]">Login to Nextbase</h1>
<p className="text-base text-left font-[400]">
Login with the account you used to signup.
</p>
</div>
<RenderProviders
providers={['google', 'github', 'twitter']}
isLoading={providerMutation.isLoading}
onProviderLoginRequested={providerMutation.mutate}
/>
<hr />
<Email
onSubmit={magicLinkMutation.mutate}
isLoading={magicLinkMutation.isLoading}
view="sign-in"
/>
<hr />
</div>
)}
</div>
);
}