Handle authentication (magic-link) through middleware
Answered
Thai posted this in #help-forum
ThaiOP
Version : 15.3.4
Middleware: edge
Architecture type: Nextjs (Frontend) - Django (Backend)
Users of my app authenticate with a magic-link : they enter email and submit > get an email with a link that redirect to my frontend with the authentication token as query param. Then this authentication token is sent to the backend and return a response with set-cookie header setting up the session cookie with CSRF. Then user is redirected to the main page.
Currently this behavior is managed on client side but I would like to move that on server side, specially in the middleware.
I did this implementation :
This implementation is working well on both local and production.
Problem is after publishing that in production, some users got errors and it was not possible for them to login because the endpoint for login with the
Do you have any ideas of how I could handle this ? Or should I keep this logic on client side ?
Middleware: edge
Architecture type: Nextjs (Frontend) - Django (Backend)
Users of my app authenticate with a magic-link : they enter email and submit > get an email with a link that redirect to my frontend with the authentication token as query param. Then this authentication token is sent to the backend and return a response with set-cookie header setting up the session cookie with CSRF. Then user is redirected to the main page.
Currently this behavior is managed on client side but I would like to move that on server side, specially in the middleware.
I did this implementation :
export default async function middleware(request: NextRequest) {
const authToken = request.nextUrl.searchParams.get('auth-token');
if (authToken)
const res = fetch('https://mybackend.com/api/login/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ authToken }),
cache: 'no-store',
});
const setCookies = res.headers.get('set-cookie');
const { sessionId, csrfToken } = parseSessionCookies(setCookies);
const clonedUrl = req.nextUrl.clone();
clonedUrl.searchParams.delete('auth-token');
clonedUrl.pathname = '/dashboard';
const nextResponse = NextResponse.redirect(clonedUrl);
nextResponse.cookies.set(sessionId);
nextResponse.cookies.set(csrfToken);
return nextResponse;
}
// other logics like checking if the sessionid cookie is present...
return NextResponse.next();
}
This implementation is working well on both local and production.
Problem is after publishing that in production, some users got errors and it was not possible for them to login because the endpoint for login with the
auth-token
was called minimum two times : first call was OK (200) and second (400) because the token is already consumed.Do you have any ideas of how I could handle this ? Or should I keep this logic on client side ?
Answered by Polar bear
Hey there!
Middleware will be called for all of your requests so it's not a good idea to do any DB calls or external fetch requests here if performance is a concern. That's also why you're likely seeing this called twice, because a request could still be in flight while the other comes in.
What's preventing you from making a next JS API route like
- accepts the token
- validates it
- sets the auth cookie
- then redirects to a dashboard?
In other words, why do you need this in middleware? Seems like an API route is what you want instead.
Middleware will be called for all of your requests so it's not a good idea to do any DB calls or external fetch requests here if performance is a concern. That's also why you're likely seeing this called twice, because a request could still be in flight while the other comes in.
What's preventing you from making a next JS API route like
/api/magicLink/:tokenId
that:- accepts the token
- validates it
- sets the auth cookie
- then redirects to a dashboard?
In other words, why do you need this in middleware? Seems like an API route is what you want instead.
7 Replies
ThaiOP
I did some research as well and I saw that some prefetch requests could pass in the middleware so I've tried to filter out them but same result :
export default async function middleware(request: NextRequest) {
const isPrefetch
= request.headers.get('next-router-prefetch') === '1'
|| request.headers.get('purpose') === 'prefetch'
|| request.headers.get('x-nextjs-data') === '1'
|| request.headers.get('rsc') === '1'
|| (request.headers.get('sec-fetch-mode') === 'cors'
&& request.headers.get('sec-fetch-dest') === 'empty'
&& request.headers.get('next-url') !== null);
if (isPrefetch) {
return NextResponse.next();
}
// ...
}
export const config = {
matcher: [
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
{ type: 'header', key: 'x-nextjs-data' },
{ type: 'header', key: 'rsc' },
],
locale: false,
},
],
};
And I don't know how to reproduce this issue, I'm a little bit stuck currently 🫠
Polar bear
Hey there!
Middleware will be called for all of your requests so it's not a good idea to do any DB calls or external fetch requests here if performance is a concern. That's also why you're likely seeing this called twice, because a request could still be in flight while the other comes in.
What's preventing you from making a next JS API route like
- accepts the token
- validates it
- sets the auth cookie
- then redirects to a dashboard?
In other words, why do you need this in middleware? Seems like an API route is what you want instead.
Middleware will be called for all of your requests so it's not a good idea to do any DB calls or external fetch requests here if performance is a concern. That's also why you're likely seeing this called twice, because a request could still be in flight while the other comes in.
What's preventing you from making a next JS API route like
/api/magicLink/:tokenId
that:- accepts the token
- validates it
- sets the auth cookie
- then redirects to a dashboard?
In other words, why do you need this in middleware? Seems like an API route is what you want instead.
Answer
ThaiOP
Okay I see, thanks a lot for your feedback, so I wanted to use the middleware to avoid a loading state for the user or to handle this through server side, but I understand now. Where do you suggest me to call this API route ? On client side ? Or directly from the email link ?
Polar bear
Directly from the email link seems like a good idea to me!
Just make sure that these tokens have an expiration and one time use of course
ThaiOP
Thanks man, I will try that 🙂