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?
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: []
...
})
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:
Create a signup api route that ensures that all user accounts that use credentials are linked to an entry in the accounts table
Initialize NextAuth using advanced initialization
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; }
In the NextAuth
jwt
options provide new functions for theencode
anddecode
. These functions should check that the auth flow is in thecallback
and also that the provider iscredentials
. 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.
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
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.Initialize
api/[...nextauth]
, and injwt
callback check whether theaccount
param iscredentials
. If the user is already in session, update thesession
andexpiration
and create, otherwise. You also have to ensure that on the client side, there is a distinction whether the user is signing usingcredentials
oroauth
, which is important for creating a customsignout
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, }, }); }
When the user signin or signup, their session is written into the database. When the user is authenticated with
oauth
, thesession
is automatically deleted. Forcredential
, however, you have to create a custom api route. Inside the api, use the built-innext-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.
© 2022 - 2024 — McMap. All rights reserved.