a11smiles
a11smiles

Reputation: 1258

How do I use cookies with context and middleware?

Here's the flow:

  1. User attempts to log in via form
  2. If login is successful, then AuthContext sets/persists a cookie of accessToken among user profile info
  3. Middleware is configured to read the cookie and validate it against identity provider
  4. If valid, the user is allowed through. If not valid, then user is redirected to login page.

Next docs say I can set outgoing, client-side cookies for the request (emphasis mine):

The cookies function allows you to read the HTTP incoming request cookies from a Server Component or write outgoing request cookies in a Server Action or Route Handler.

My Next.js app has the following structure:

<AuthContext>
  <Layout>
     <Page>
     ...
     </Page>
  </Layout>
</AuthContext>

Current code:

// AuthProvider 

"use client";
import { AuthContextType, AuthenticatedUser } from "@/app/@types/auth";
import { ReactNode, createContext, useState } from "react";
import { cookies } from "next/headers";

const AuthContext = createContext<AuthContextType | null>(null);

interface Props {
    children?: ReactNode;
}

function AuthProvider({ children }: Props) {
    const cookieJar = cookies();

    function login(user, persist: boolean) {
        const userJson = JSON.stringify({
            uid: user.uid,
            email: user.email,
            emailVerified: user.emailVerified,
            displayName: user.displayName,
            refreshToken: user.stsTokenManager.refreshToken,
            accessToken: user.stsTokenManager.accessToken,
            expirationTime: user.stsTokenManager.expirationTime
        });

        cookieJar.set({
            name: "auth",
            value: userJson,
            httpOnly: true,
            expires: persist ? undefined : Infinity
        });
    }
   
    ... // Removed for brevity
}
// middleware.ts

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
    let valid = false;

    // check validity

    if (!valid)
        return NextResponse.redirect(new URL('/auth/login', request.url))
}

export const config = {
    matcher: '/dashboard/:path*',
}

Here's my issues and what I can't figure out.

AuthContext is client-side, whereas middleware.ts is (currently) a server component (as it's importing next/headers).

If I attempt to run as-is, I get the following error due to the createContext of my AuthContext:

You're importing a component that needs next/headers. That only works in a Server Component but one of its parents is marked with "use client", so it's a Client Component.

If I remove use client from my AuthContext then I get the following error:

You're importing a component that needs createContext. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.

How do I fix this? Specifically, how do I set cookies, then use middleware to read them for authentication purposes? I think I've got all of the pipes in place, but not sure how to hook them up.

Upvotes: 0

Views: 994

Answers (1)

kblza
kblza

Reputation: 21

Cookies are shared between client and server, so you need to just access request.cookies.get('your-cookie') at middleware function.

export async function middleware(request: NextRequest) {
    const access_token = request.cookies.get('your-cookie')
    if (!!access_token) {
        return NextResponse.next()
    }
    return NextResponse.rewrite(new URL('/', request.url))
}

Upvotes: -1

Related Questions