Improve user session handling in middleware

The changes add user session handling directly in the middleware. This ensures the user data is fetched at the start of a request and then passed on to route handlers, reducing repeated data fetching. Also, these improvements include adjustments for how sign-out and auth-change events are managed, particularly when the user session state changes. Additionally, it corrects the error response from useUser hook to return `undefined` instead of `null`.
This commit is contained in:
giancarlo
2024-04-28 13:20:25 +07:00
parent 3efbf6029f
commit 51a90bde83
4 changed files with 31 additions and 11 deletions

View File

@@ -1,6 +1,8 @@
import type { NextRequest } from 'next/server'; import type { NextRequest } from 'next/server';
import { NextResponse, URLPattern } from 'next/server'; import { NextResponse, URLPattern } from 'next/server';
import type { UserResponse } from '@supabase/supabase-js';
import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs'; import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa'; import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa';
@@ -20,12 +22,16 @@ export const config = {
export async function middleware(request: NextRequest) { export async function middleware(request: NextRequest) {
const response = NextResponse.next(); const response = NextResponse.next();
const supabase = createMiddlewareClient(request, response);
// get the user from the session (no matter if it's logged in or not)
const userResponse = await supabase.auth.getUser();
// set a unique request ID for each request // set a unique request ID for each request
// this helps us log and trace requests // this helps us log and trace requests
setRequestId(request); setRequestId(request);
// apply CSRF and session middleware // apply CSRF protection for mutating requests
const csrfResponse = await withCsrfMiddleware(request, response); const csrfResponse = await withCsrfMiddleware(request, response);
// handle patterns for specific routes // handle patterns for specific routes
@@ -33,7 +39,11 @@ export async function middleware(request: NextRequest) {
// if a pattern handler exists, call it // if a pattern handler exists, call it
if (handlePattern) { if (handlePattern) {
const patternHandlerResponse = await handlePattern(request, csrfResponse); const patternHandlerResponse = await handlePattern(
request,
csrfResponse,
userResponse,
);
// if a pattern handler returns a response, return it // if a pattern handler returns a response, return it
if (patternHandlerResponse) { if (patternHandlerResponse) {
@@ -41,7 +51,8 @@ export async function middleware(request: NextRequest) {
} }
} }
// if no pattern handler returned a response, return the session response // if no pattern handler returned a response,
// return the session response
return csrfResponse; return csrfResponse;
} }
@@ -149,9 +160,13 @@ function getPatterns() {
}, },
{ {
pattern: new URLPattern({ pathname: '/home*' }), pattern: new URLPattern({ pathname: '/home*' }),
handler: async (req: NextRequest, res: NextResponse) => { handler: async (
const supabase = createMiddlewareClient(req, res); req: NextRequest,
const { data: user, error } = await supabase.auth.getUser(); res: NextResponse,
userResponse: UserResponse,
) => {
const { data: user, error } = userResponse;
const origin = req.nextUrl.origin; const origin = req.nextUrl.origin;
const next = req.nextUrl.pathname; const next = req.nextUrl.pathname;
@@ -163,6 +178,8 @@ function getPatterns() {
return NextResponse.redirect(new URL(redirectPath, origin).href); return NextResponse.redirect(new URL(redirectPath, origin).href);
} }
const supabase = createMiddlewareClient(req, res);
const requiresMultiFactorAuthentication = const requiresMultiFactorAuthentication =
await checkRequiresMultiFactorAuthentication(supabase); await checkRequiresMultiFactorAuthentication(supabase);

View File

@@ -34,7 +34,7 @@ export function useAuthChangeListener({
useEffect(() => { useEffect(() => {
// keep this running for the whole session unless the component was unmounted // keep this running for the whole session unless the component was unmounted
const listener = client.auth.onAuthStateChange((_, user) => { const listener = client.auth.onAuthStateChange((event, user) => {
// log user out if user is falsy // log user out if user is falsy
// and if the current path is a private route // and if the current path is a private route
const shouldRedirectUser = const shouldRedirectUser =
@@ -47,6 +47,10 @@ export function useAuthChangeListener({
return; return;
} }
if (event === 'SIGNED_OUT') {
return router.refresh();
}
if (accessToken) { if (accessToken) {
const isOutOfSync = user?.access_token !== accessToken; const isOutOfSync = user?.access_token !== accessToken;

View File

@@ -1,15 +1,13 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import { useSupabase } from './use-supabase'; import { useSupabase } from './use-supabase';
export function useSignOut() { export function useSignOut() {
const client = useSupabase(); const client = useSupabase();
const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async () => { mutationFn: async () => {
await client.auth.signOut(); await client.auth.signOut();
await queryClient.invalidateQueries();
}, },
}); });
} }

View File

@@ -14,7 +14,7 @@ export function useUser(initialData?: User | null) {
// this is most likely a session error or the user is not logged in // this is most likely a session error or the user is not logged in
if (response.error) { if (response.error) {
return null; return undefined;
} }
if (response.data?.user) { if (response.data?.user) {
@@ -28,6 +28,7 @@ export function useUser(initialData?: User | null) {
queryFn, queryFn,
queryKey, queryKey,
initialData, initialData,
refetchOnMount: false,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
}); });
} }