You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm using next-auth for a website that I'm creating in order to allow google authentication for only one spesific emial address (kept in process.env.ALLOWED_EMAIL).
I'm using nextjs 14.2.3 and next-auth 4.24.7 and a custom firebase adapter (the official one is not compatible with the latest firebase-admin version).
In my middleware I'm making sure that only the allowed email could be autherized:
every single time I try to sign in with google and the correct email, I get redirected by the middleware back to the signIn page and I can see in the console that the token provided to the autherize callback in middleware is null.
my config options for the nextAuth route handler:
// adminFirestore is an instance of Firestore from "firebase-admin/firestore"import{adminFirestore}from'@/firebase/admin';import{FirestoreAdapter}from'./firebaseAdapter';import{NextAuthOptions}from'next-auth';importGoogleProviderfrom'next-auth/providers/google';constoptions: NextAuthOptions={adapter: FirestoreAdapter(adminFirestore),providers: [GoogleProvider({clientId: process.env.GOOGLE_CLIENT_ID!,clientSecret: process.env.GOOGLE_CLIENT_SECRET!,}),],callbacks: {signIn({ user, account }){return(account?.provider=='google'&&user.email==process.env.ALLOWED_EMAIL);},redirect({ url, baseUrl }){if(url.startsWith('/'))return`${baseUrl}${url}`;elseif(newURL(url).origin===baseUrl)returnurl;returnbaseUrl;},},debug: false,secret: process.env.NEXTAUTH_SECRET,};exportdefaultoptions;
customized firebase adapter (here too I'm checking in createUser that the email is the right one):
// copied from @auth/firebase-adapter and modified/*Copyright (c) 2022-2024, Balázs OrbánPermission to use, copy, modify, and/or distribute this software for anypurpose with or without fee is hereby granted, provided that the abovecopyright notice and this permission notice appear in all copies.THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIESWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OFMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FORANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGESWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN ANACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OFOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.*/import{Firestore,Timestamp}from'firebase-admin/firestore';importtype{Adapter,AdapterUser,AdapterAccount,AdapterSession,VerificationToken,}from'next-auth/adapters';exportfunctionFirestoreAdapter(db: Firestore): Adapter{constC=collectionsFactory(db);return{asynccreateUser(userInit){if(userInit.email!=process.env.ALLOWED_EMAIL){thrownewError('unauthorized');}const{id: userId}=awaitC.users.add(userInitasAdapterUser);constuser=awaitgetDoc(C.users.doc(userId));if(!user)thrownewError('[createUser] Failed to fetch created user');returnuser;},asyncgetUser(id){returnawaitgetDoc(C.users.doc(id));},asyncgetUserByEmail(email){returnawaitgetOneDoc(C.users.where('email','==',email));},asyncgetUserByAccount({ provider, providerAccountId }){constaccount=awaitgetOneDoc(C.accounts.where('provider','==',provider).where('providerAccountId','==',providerAccountId));if(!account)returnnull;returnawaitgetDoc(C.users.doc(account.userId));},asyncupdateUser(partialUser){if(!partialUser.id)thrownewError('[updateUser] Missing id');constuserRef=C.users.doc(partialUser.id);awaituserRef.set(partialUser,{merge: true});constuser=awaitgetDoc(userRef);if(!user)thrownewError('[updateUser] Failed to fetch updated user');returnuser;},asyncdeleteUser(userId){awaitdb.runTransaction(asynctransaction=>{constaccounts=awaitC.accounts.where('userId','==',userId).get();constsessions=awaitC.sessions.where('userId','==',userId).get();transaction.delete(C.users.doc(userId));accounts.forEach(account=>transaction.delete(account.ref));sessions.forEach(session=>transaction.delete(session.ref));});},asynclinkAccount(accountInit){constref=awaitC.accounts.add(accountInit);constaccount=awaitref.get().then(doc=>doc.data());returnaccount??null;},asyncunlinkAccount({ provider, providerAccountId }){awaitdeleteDocs(C.accounts.where('provider','==',provider).where('providerAccountId','==',providerAccountId).limit(1));},asynccreateSession(sessionInit){constref=awaitC.sessions.add(sessionInit);constsession=awaitref.get().then(doc=>doc.data());if(session)returnsession??null;thrownewError('[createSession] Failed to fetch created session');},asyncgetSessionAndUser(sessionToken){constsession=awaitgetOneDoc(C.sessions.where('sessionToken','==',sessionToken));if(!session)returnnull;constuser=awaitgetDoc(C.users.doc(session.userId));if(!user)returnnull;return{ session, user };},asyncupdateSession(partialSession){constsessionId=awaitdb.runTransaction(asynctransaction=>{constsessionSnapshot=(awaittransaction.get(C.sessions.where('sessionToken','==',partialSession.sessionToken).limit(1))).docs[0];if(!sessionSnapshot?.exists)returnnull;transaction.set(sessionSnapshot.ref,partialSession,{merge: true});returnsessionSnapshot.id;});if(!sessionId)returnnull;constsession=awaitgetDoc(C.sessions.doc(sessionId));if(session)returnsession;thrownewError('[updateSession] Failed to fetch updated session');},asyncdeleteSession(sessionToken){awaitdeleteDocs(C.sessions.where('sessionToken','==',sessionToken).limit(1));},asynccreateVerificationToken(verificationToken){awaitC.verification_tokens.add(verificationToken);returnverificationToken;},asyncuseVerificationToken({ identifier, token }){constverificationTokenSnapshot=(awaitC.verification_tokens.where('identifier','==',identifier).where('token','==',token).limit(1).get()).docs[0];if(!verificationTokenSnapshot)returnnull;constdata=verificationTokenSnapshot.data();awaitverificationTokenSnapshot.ref.delete();returndata;},};}/** @internal */functiongetConverter<DocumentextendsRecord<string,any>>(options: {excludeId?: boolean;}): FirebaseFirestore.FirestoreDataConverter<Document>{return{toFirestore(object){constdocument: Record<string,unknown>={};for(constkeyinobject){if(key==='id')continue;constvalue=object[key];if(value!==undefined){document[key]=value;}else{console.warn(`FirebaseAdapter: value for key "${key}" is undefined`);}}returndocument;},fromFirestore(snapshot: FirebaseFirestore.QueryDocumentSnapshot<Document>): Document{constdocument=snapshot.data()!;// we can guarantee it existsconstobject: Record<string,unknown>={};if(!options?.excludeId){object.id=snapshot.id;}for(constkeyindocument){letvalue: any=document[key];if(valueinstanceofTimestamp)value=value.toDate();object[key]=value;}returnobjectasDocument;},};}/** @internal */exportasyncfunctiongetOneDoc<T>(querySnapshot: FirebaseFirestore.Query<T>): Promise<T|null>{constquerySnap=awaitquerySnapshot.limit(1).get();returnquerySnap.docs[0]?.data()??null;}/** @internal */asyncfunctiondeleteDocs<T>(querySnapshot: FirebaseFirestore.Query<T>): Promise<void>{constquerySnap=awaitquerySnapshot.get();for(constdocofquerySnap.docs){awaitdoc.ref.delete();}}/** @internal */exportasyncfunctiongetDoc<T>(docRef: FirebaseFirestore.DocumentReference<T>): Promise<T|null>{constdocSnap=awaitdocRef.get();returndocSnap.data()??null;}/** @internal */exportfunctioncollectionsFactory(db: FirebaseFirestore.Firestore){return{users: db.collection('users').withConverter(getConverter<AdapterUser>({})),sessions: db.collection('sessions').withConverter(getConverter<AdapterSession>({})),accounts: db.collection('accounts').withConverter(getConverter<AdapterAccount>({})),verification_tokens: db.collection('verificationTokens').withConverter(getConverter<VerificationToken>({excludeId: true})),};}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
I'm using next-auth for a website that I'm creating in order to allow google authentication for only one spesific emial address (kept in process.env.ALLOWED_EMAIL).
I'm using nextjs 14.2.3 and next-auth 4.24.7 and a custom firebase adapter (the official one is not compatible with the latest firebase-admin version).
In my middleware I'm making sure that only the allowed email could be autherized:
every single time I try to sign in with google and the correct email, I get redirected by the middleware back to the signIn page and I can see in the console that the token provided to the autherize callback in middleware is null.
my config options for the nextAuth route handler:
customized firebase adapter (here too I'm checking in createUser that the email is the right one):
Beta Was this translation helpful? Give feedback.
All reactions