GithubHelp home page GithubHelp logo

lowfront / next-auth-firestore-adapter-example Goto Github PK

View Code? Open in Web Editor NEW
22.0 1.0 5.0 199 KB

firestore adapter example in next-auth

Home Page: https://next-auth-firestore-todo-mvc.vercel.app

TypeScript 98.44% JavaScript 0.42% CSS 1.13%
firebase firebase-auth firebase-authentication firestore next-auth next-auth-provider nextjs todomvc

next-auth-firestore-adapter-example's Introduction

Firebase adepter auth process with custom token example in Next Auth

Example of a firebase adapter that works with firebase authentication. A firebase is a database that has rules functionality for use by both servers and clients. If use firebase on a client, if rule is not set, all data accessible to the client is accessible to anyone who can read the code. When storing user, account, and session data in the next-adapter in the firebase, if rule is not set, all data will be public. This example uses the method of sign in using customToken to protect the data in the firebase at the frontend. After sign in through next-auth's provider, allow api endpoint that issues customToken of the firebase service on the server. When working with the firebase database at the frontend, if the client is not currently sign in as customToken, the user receives a token from the customToken api endpoint and then sign in and proceeds with the database operation. This example was created as a fireestore target, but can also use the same method for the firebase realtime database if necessary.

Database Structure

firestore root
├── _next_auth_firebase_adapter_ [collection]
│  └── store [document]
│     ├── account [collection] # account scheme from next-auth
│     ├── session [collection] # session scheme from next-auth
│     ├── user [collection] # user scheme from next-auth
│     └── customToken [collection]
│        └── <session token> [document] # same as session token in next-auth and when issuing custom token, it is included in additional claims.
│           ├── exires [field]
│           └── token [field]
└── store [collection]
   └── <user email> [document]
      └── store [collection]
         └── <user data uid> [document]
            ├── checked [field]
            └── label [field]
  1. Go to firebase console and select your project.

  2. In Project settings > Service accounts > Firebase Admin SDK, for server, click "Generate new private key" and download "Generate Key" and write it in the FIREBASE_ADMIN_CONFIG_ key of .env.local.

  3. In Project settings > General, Click "Add app" at the bottom of the page, add a web app for frontend, and record the contents in the .env.local file.

  4. Add a new rule as follow in Firestore Database > Rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
    match /store/{userId}/{document=**} {
    	allow read, write: if request.auth.token.id == userId;
// && exists(/databases/$(database)/documents/_next_auth_firebase_adapter_/store/customToken/$(request.auth.token.sessionToken)); // To enhance security, after sign out, the issued custom token is deactivated and has a session method security level, but database read costs are added for each task.
    }
  }
}
  1. Add adapter to pages/api/auth/[...nextauth].ts.
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
  ],
  adapter: FirebaseAdapter(db),
});
  1. Add custom token endpoint to pages/api/auth/token.ts.
export type CustomToken = {
  token: string;
  expires: string; // date
};

export async function getCustomToken(sessionToken: string) {
  const tokenDocRef = db.collection('_next_auth_firebase_adapter_').doc('store').collection('customToken').doc(sessionToken);
  const tokenDoc = await tokenDocRef.get();
  if (!tokenDoc.exists) return;
  const { token, expires } = tokenDoc.data() as CustomToken;
  if (Date.now() > new Date(expires).getTime()) return;
  return token;
}

export async function updateCustomToken(sessionToken: string, token: string) {
  const tokenDocRef = db.collection('_next_auth_firebase_adapter_').doc('store').collection('customToken').doc(sessionToken);

  await tokenDocRef.set({
    token,
    expires: Date.now() + 60 * 60 * 1000,
  });

  return token;
}

export function getSessionToken(req: NextApiRequest) {
  return req.cookies['__Secure-next-auth.session-token'] ?? req.cookies['next-auth.session-token'];
}

async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'GET') return res.status(403).json(false);
  const session = await getSession({ req }) as Session;
  if (!session) return res.status(403).json(false);
  const sessionToken = getSessionToken(req);
  const { user } = session as unknown as {
    user: NonNullable<Session['user']>;
  };
  const email = user.email as string;
  let token = await getCustomToken(sessionToken);
  if (token) return res.json(token);

  token = await admin
    .auth()
    .createCustomToken(email, Object.assign({}, additionalClaims?.(session), { sessionToken }));

  await updateCustomToken(sessionToken, token);

  return res.json(token);
};
  1. Run npm run dev

Now use your firebase data in the client, or even if the firebaseConfig is exposed, the data in the next-auth is protected private. If an commented rule is added, customToken issued after sign out is not available.

next-auth-firestore-adapter-example's People

Contributors

lowfront avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

next-auth-firestore-adapter-example's Issues

when is removeExpiredSessions used?

Hello again :-)
I found the removeExpriedSessions function in the firebase-server.ts file

removeExpriedSessions says " // Expired session deletion function, used for cron or api"

But i cant find the cron job or api call? I guess i'm supposed to develop that? is there a suggested implementation method?

Also i found the refreshAccessToken function in https://next-auth.js.org/tutorials/refresh-token-rotation

but it's also not referenced or called anywhere - the tutorial has it called in the JWT callback but we don't use that callback since we're persisting to firestore right? So is there a suggested implementation for either of these?

Thank you,
Alex

export async function removeExpiredSessions(
  limit: number = 100,
  asyncMax: number = 30
) {
  // Expired session deletion function, used for cron or api
  const adapter = FirebaseAdapter(db);

  const q = db
    .collection(`${ADAPTER_COLLECTION_NAME}/auth_store/session`)
    .where("expires", "<", new Date())
    .limit(limit);
  const expiredSessionDocs = await findMany(q);
  await asyncMap(
    expiredSessionDocs.map(
      (doc) => () =>
        adapter.deleteSession(doc.data().sessionToken) as Promise<void>
    ),
    asyncMax
  );
}

Questions regarding custom tokens

Hello, thank you for putting this important example together!

my app will use tokens from two different providers - google and github to make different calls.

With google I will initially log the user in with their Profile scope and later I will upgrade their scope to get the API we need access to.

Can you explain a little how this should work?

After running your example Im getting an error that the user cannot access the data - if i open up the access rules, then they can.

I've spent a couple of days on this already TBH and still cant figure out how to get the tokens to the user or how to auth the user to write to the database with the rules locked down properly.

thank you!!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.