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:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user