Next-Auth credentials not returning session and not storing Session and Account in db via prisma adapter
Asked Answered
G

3

7

i'm trying to implement credientials part with next-auth, github and google works like charm and after signin via these providers I get user, account, session storred correctly in db psql via prisma adapter, while when using credentials i get 200 from api when user and pass correct, but session apears only in network dev tab, but is null when using getSession. When user is registering, he correctly gets userId very much like when github and google are used, but Account and Session tab are not filled, should I make it manually, like refreshToken, accessToken expires and manually add data for specific userId and store it in Account and Session tables in db?

Gershom answered 2/5, 2022 at 17:35 Comment(0)
H
11

The CredentialsProvider is not compatible with Database Sessions. In order for credentials to work, you need to configure Next-Auth to use JWT sessions instead of Database sessions.

This can be done by setting the "session" option within Next-Auth

export default NextAuth({
   session: {
      strategy: "jwt",
      maxAge: 3000,
   }
   providers: []
   ...
})
Horseflesh answered 6/6, 2022 at 21:48 Comment(0)
A
7

As pointed out by @Mike the Credentials Provider is restricted to using only JWT by the next-auth developers. However, you can implement a workaround to have it leverage the database sessions by doing the following:

  1. Create a signup api route that ensures that all user accounts that use credentials are linked to an entry in the accounts table

  2. Initialize NextAuth using advanced initialization

  3. Register a signIn callback as a part of the NextAuth callback options that uses the database adapter provided to NextAuth to create a session using the createSession method.

     async signIn({ user, account, profile, email, credentials }) {
         // Check if this sign in callback is being called in the credentials authentication flow. If so, use the next-auth adapter to create a session entry in the database (SignIn is called after authorize so we can safely assume the user is valid and already authenticated).
         if (req.query.nextauth.includes('callback') && req.query.nextauth.includes('credentials') && req.method === 'POST') {
             if (user) {
                 const sessionToken // Implement a function to generate the session token (you can use randomUUID as an example)
                 const sessionExpiry // Implement a function to calculate the session cookie expiry date
    
                 await adapter.createSession({
                     sessionToken: sessionToken,
                     userId: user.id,
                     expires: sessionExpiry
                 })
    
                 const cookies = new Cookies(req,res)
    
                 cookies.set('next-auth.session-token', sessionToken, {
                     expires: sessionExpiry
                 })
             }   
         }
    
         return true;
     }
    
  4. In the NextAuth jwt options provide new functions for the encode and decode. These functions should check that the auth flow is in the callback and also that the provider is credentials. When using the credentials provider is should return as a String the sessionToken in the encode and always return null for decode. Outside the credentials is should do the normal JWT encode and decode behaviours.

    jwt: {
         // Customize the JWT encode and decode functions to overwrite the default behaviour of storing the JWT token in the session cookie when using credentials providers. Instead we will store the session token reference to the session in the database.
         encode: async (token, secret, maxAge) => {
             if (req.query.nextauth.includes('callback') && req.query.nextauth.includes('credentials') && req.method === 'POST') {
                 const cookies = new Cookies(req,res)
    
                 const cookie = cookies.get('next-auth.session-token')
    
                 if(cookie) return cookie; else return '';
    
             }
             // Revert to default behaviour when not in the credentials provider callback flow
             return encode(token, secret, maxAge)
         },
         decode: async (token, secret) => {
             if (req.query.nextauth.includes('callback') && req.query.nextauth.includes('credentials') && req.method === 'POST') {
                 return null
             }
    
             // Revert to default behaviour when not in the credentials provider callback flow
             return decode(token, secret)
         }
     },
    

Both functions above must be defined within the auth(req, res) handler for [...nextauth].js api route as they need to be in the same scope to have access to the raw http requests as well as the options provided by NextAuth.

Example code can be found under the responses for GitHub issue #4394 or for a more detailed explanation read this blog post I created.

Audio answered 1/8, 2022 at 17:41 Comment(0)
H
0

In next-auth [documentation][1], it clearly states there may be some security constraints when persisting credentials in the database.

Given the limitations and constraints in the documentation, here is a workaround using Prisma. And here I will assume that you have set up api route to signup and /authenticate-credential

  1. Create a custom session token function, which it can easily be created using randomUUID. This function, generateSession, is used to authenticate the session and save it into the database.

  2. Initialize api/[...nextauth], and in jwt callback check whether the account param is credentials. If the user is already in session, update the session and expiration and create, otherwise. You also have to ensure that on the client side, there is a distinction whether the user is signing using credentials or oauth, which is important for creating a custom signout api route.

                    const userIsInSession = await prisma.session.findFirst({
                    where: { userId: credentialUser.user.id },
                 });
    
                 if (userIsInSession) {
                    await prisma.session.update({
                       where: { id: userIsInSession.id },
                       data: { sessionToken: sessionToken, expires: sessionExpiry },
                    });
                 } else {
                    await prisma.session.create({
                       data: {
                          sessionToken: sessionToken,
                          userId: credentialUser.user.id,
                          expires: sessionExpiry,
                       },
                    });
                 }
    
  3. When the user signin or signup, their session is written into the database. When the user is authenticated with oauth, the session is automatically deleted. For credential, however, you have to create a custom api route. Inside the api, use the built-in next-auth function, decode to parse the cookie. const decoded = (await decode({ secret: secret, token, })), where the secret should be kept in the .env file. After parsing it, delete the session from the database.

Holytide answered 30/1 at 21:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.