lucia-auth / lucia

Authentication, simple and clean
https://lucia-auth.com
MIT License
8.61k stars 448 forks source link

[Bug]: Invalid Code Verifier when using Google oAuth in nextjs app dir #1400

Closed karyanayandi closed 5 months ago

karyanayandi commented 5 months ago

Package

lucia

Describe the bug

I try use google oath following lucia and arctic documentation but always return invalid code verifier when i try to sign in. 2024-02-02_16-53

this is is lucia.ts code

import { DrizzleSQLiteAdapter } from "@lucia-auth/adapter-drizzle"
import { Google } from "arctic"
import { Lucia } from "lucia"

import { env } from "@/env.mjs"
import { db } from "@/lib/db"
import { sessions, users } from "@/lib/db/schema/auth"

const adapter = new DrizzleSQLiteAdapter(db, sessions, users)

export const lucia = new Lucia(adapter, {
  sessionCookie: {
    expires: false,
    attributes: {
      secure: process.env.NODE_ENV === "production",
    },
  },
  getUserAttributes: (attributes) => {
    return {
      name: attributes.name,
      email: attributes.email,
      image: attributes.image,
    }
  },
})

export const google = new Google(
  env.GOOGLE_CLIENT_ID,
  env.GOOGLE_CLIENT_SECRET,
  "http://localhost:3000/login/google/callback",
)

declare module "lucia" {
  interface Register {
    Lucia: typeof lucia
    DatabaseUserAttributes: DatabaseUserAttributes
  }
}

interface DatabaseUserAttributes {
  name: string
  username: string
  email: string
  image: string
}

this is app/login/google/route.ts code

import { cookies } from "next/headers"
import { generateCodeVerifier, generateState } from "arctic"

import { google } from "@/lib/auth/lucia"

export async function GET(): Promise<Response> {
  const state = generateState()
  const codeVerifier = generateCodeVerifier()

  const url = await google.createAuthorizationURL(state, codeVerifier)

  cookies().set("state", state, {
    path: "/",
    // secure: process.env.NODE_ENV === "production",
    secure: false,
    httpOnly: true,
    maxAge: 60 * 10,
    sameSite: "lax",
  })

  cookies().set("code_verifier", state, {
    path: "/",
    // secure: process.env.NODE_ENV === "production",
    secure: false,
    httpOnly: true,
    maxAge: 60 * 10,
    sameSite: "lax",
  })

  return Response.redirect(url)
}

this is app/login/google/callback/route.ts code

import { cookies } from "next/headers"
import { OAuth2RequestError } from "arctic"

import { google, lucia } from "@/lib/auth/lucia"
import { db, initializeDB } from "@/lib/db"
import { accounts, users } from "@/lib/db/schema/auth"
import { cuid, slugify } from "@/lib/utils"

export async function GET(request: Request): Promise<Response> {
  const url = new URL(request.url)
  const code = url.searchParams.get("code")
  const state = url.searchParams.get("state")

  const storedState = cookies().get("state")?.value ?? null
  const storedCodeVerifier = cookies().get("code_verifier")?.value ?? null

  if (
    !code ||
    !state ||
    !storedState ||
    !storedCodeVerifier ||
    state !== storedState
  ) {
    return new Response("Invalid Request", {
      status: 400,
    })
  }

  try {
    const tokens = await google.validateAuthorizationCode(
      code,
      storedCodeVerifier,
    )

    const googleUserResponse = await fetch(
      "https://openidconnect.googleapis.com/v1/userinfo",
      {
        headers: {
          Authorization: `Bearer ${tokens.accessToken}`,
        },
      },
    )

    const googleUser: GoogleUser = await googleUserResponse.json()

    const existingUser = await initializeDB.query.accounts.findFirst({
      where: (accounts, { and, eq }) =>
        and(
          eq(accounts.providerId, "google"),
          eq(accounts.providerAccountId, googleUser.id),
        ),
    })

    if (existingUser) {
      const session = await lucia.createSession(existingUser.userId, {})
      const sessionCookie = lucia.createSessionCookie(session.id)
      cookies().set(
        sessionCookie.name,
        sessionCookie.value,
        sessionCookie.attributes,
      )
      return new Response(null, {
        status: 302,
        headers: {
          Location: "/",
        },
      })
    }

    const userId = cuid()

    await db.insert(users).values({
      id: userId,
      username: `${slugify(googleUser.name)}_${cuid()}`,
      email: googleUser.email,
      image: googleUser.image,
    })

    await db.insert(accounts).values({
      providerId: "google",
      providerAccountId: googleUser.id,
      userId: userId,
    })

    const session = await lucia.createSession(userId, {})
    const sessionCookie = lucia.createSessionCookie(session.id)
    cookies().set(
      sessionCookie.name,
      sessionCookie.value,
      sessionCookie.attributes,
    )
    return new Response(null, {
      status: 302,
      headers: {
        Location: "/",
      },
    })
  } catch (e) {
    console.log(e)
    // the specific error message depends on the provider
    if (e instanceof OAuth2RequestError) {
      // invalid code
      return new Response(null, {
        status: 400,
      })
    }
    return new Response(null, {
      status: 500,
    })
  }
}

interface GoogleUser {
  id: string
  name: string
  email: string
  image: string
}
pilcrowOnPaper commented 5 months ago

Can you open an issue in https://github.com/pilcrowonpaper/arctic instead? Also you don't need to paste code not related to Arctic

karyanayandi commented 5 months ago

Can you open an issue in https://github.com/pilcrowonpaper/arctic instead? Also you don't need to paste code not related to Arctic

oke. thank you

pilcrowOnPaper commented 5 months ago

You're storing state instead of code_verifier

cookies().set("code_verifier", state, {
    path: "/",
    // secure: process.env.NODE_ENV === "production",
    secure: false,
    httpOnly: true,
    maxAge: 60 * 10,
    sameSite: "lax",
  })
karyanayandi commented 5 months ago

You're storing state instead of code_verifier

cookies().set("code_verifier", state, {
    path: "/",
    // secure: process.env.NODE_ENV === "production",
    secure: false,
    httpOnly: true,
    maxAge: 60 * 10,
    sameSite: "lax",
  })

ahh my bad. thank you for your help.