auth authentication but i'm having an issue with sessions
My next-auth version is 4.0.0-beta.4(also tried beta.7 with same results)
I have my own JWT token backend that takes a username and password. And gives back an object with accesstoken, refreshtoken, expiretime and resfresh-time
So im trying to use that backend to handle session state with next-auth.
I manage to set the cookie "next-auth.session-token". But the session is always undefined when i'm trying to getSession.
And i don't have any sessions in my firefox browser.
const options = {
providers: [
Credentials({
name: "Credentials",
credentials: {
username: {
label: "Username",
type: "text"
},
password: {
label: "Password",
type: "password"
}
},
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60 // the session will last 30 days
},
authorize: async (credentials) => {
const tokenUrl = "http://192.168.0.8:8081/api/auth/token"
const token = await fetch(tokenUrl, {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: credentials.username,
password: credentials.password
})
})
.then(res => res.json())
console.log("token: ", token)
if (token) {
const userUrl = "http://192.168.0.8:8081/admin/user/username/" + credentials.username;
const user = await fetch(userUrl, {
method: "GET",
mode: "cors",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token.access_token
}
}).then(res => res.json())
return {
token,
user
};
} else {
return null;
}
}
}),
],
session: {
jwt: true
},
pages: {
signIn: "/login",
},
secret: "TEST",
callbacks: {
async jwt({ token, user }) {
// Initial call
if (user) {
return {
accessToken: user.token.access_token,
accessTokenExpires: Date.now() + user.token.expire_time * 1000,
refreshToken: user.token.refresh_token,
user: user.user,
}
}
// Subsequent calls
return token;
},
async session(session) {
session.name = session.token.user.fullName
session.accessToken = session.token.accessToken
session.refreshToken = session.token.refreshToken
return session;
}
}
}
Here i'm trying to get the session after logging in
export default function Home() {
const { data: session } = getSession();
console.log("session: ", session)
return (
< div >
</div >
)
}
Any ideas?
My problem was that I was using the wrong method. I needed to use the useSession method instead of getSession. Then it worked. 🙂
Related
im building a nextjs application using next-auth. For now i have login with google, credentials and github.If i make login with google that contain the email "example#gmail.com" and then i logout, if i try make login with github but with a account that have the same email "example#gmail.com", i get the error: OAuthAccountNotLinked
In the database model that next-auth provides to us, User have relation with Accounts that is an array, so i think that would be possible have the sabe user but liked with 2 accounts. I forgot something or this is the default behavior?
My [...nextAuth].ts code
export const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const data = requestWrapper(req, res);
return await NextAuth(...data);
};
export default handler;
export function requestWrapper(
req: NextApiRequest,
res: NextApiResponse
): [req: NextApiRequest, res: NextApiResponse, opts: NextAuthOptions] {
const generateSessionToken = () => randomUUID();
const fromDate = (time: number, date = Date.now()) =>
new Date(date + time * 1000);
const adapter = PrismaAdapter(prisma);
const opts: NextAuthOptions = {
// Include user.id on session
adapter: adapter,
pages: {
signIn: "/login",
},
callbacks: {
session({ session, user }) {
console.log("AAAA");
if (session.user) {
session.user = user;
}
return session;
},
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 = generateSessionToken();
const sessionMaxAge = 60 * 60 * 24 * 30; //30Daysconst sessionMaxAge = 60 * 60 * 24 * 30; //30Days
const sessionExpiry = fromDate(sessionMaxAge);
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;
},
},
jwt: {
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 });
},
},
// Configure one or more authentication providers
secret: process.env.NEXTAUTH_SECRET,
// debug: true,
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID as string,
clientSecret: process.env.GITHUB_SECRET as string,
profile(profile, token) {
return {
id: profile.id.toString(),
name: profile.name || profile.login,
image: profile.avatar_url,
email: profile.email,
role: Role.USER,
};
},
}),
GoogleProvider({
clientId: process.env.GOOGLE_ID as string,
clientSecret: process.env.GOOGLE_SECRET as string,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
},
},
}),
CredentialProvider({
name: "CredentialProvider",
credentials: {
email: { label: "Email", type: "text", placeholder: "" },
password: { label: "Password", type: "password" },
},
async authorize(credentials: any, _req): Promise<any | null> {
const userInputs = {
email: credentials.email,
password: credentials.password,
};
const { user } = await loginCredentials(userInputs);
if (user) {
return user;
} else {
return null;
}
},
}),
],
};
return [req, res, opts];
}
I am using next auth in my next.js project,
but during development, if the server refreshes then the sessio immediately loses the data in it, also if I change to another tab in the browser and then return back to it, the session loses its data, and I will be forced to sign out then sign in to let it work again.
This doesn't happen in production mode.
below it the [...nextauth].js file code:
import NextAuth from "next-auth/next";
import GoogleProvider from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";
let userInfo = {};
export default NextAuth({
site: process.env.SITE,
providers: [
CredentialsProvider({
name: "Credentials",
async authorize(credentials, req) {
let status;
let user = {};
let message = ''
await axios
.post(
`${process.env.NEXT_PUBLIC_DOMAIN_URL}/app/auth`,
{ userName: credentials.username.replace(/\s/g, ''), pwd: credentials.password },
{
headers: { "Content-Type": "application/json" },
withCredentials: true,
}
)
.then(function (response) {
if (response?.data) {
user = { ...response.data };
userInfo = user;
}
})
.catch((error) => {
if (error.response) {
throw new Error(JSON.stringify({ errors: error?.response?.data?.message, status: false }))
} else if (error.request) {
throw new Error(JSON.stringify({ errors: 'Server error, please try again.', status: false }))
} else {
throw new Error(JSON.stringify({ errors: 'Network error, please try again.', status: false }))
}
});
return user;
},
}),
],
pages: {
signIn: "/Auth/Login",
},
session: {
jwt: true,
strategy: "jwt"
},
jwt: {
maxAge: 60 * 60 * 24 * 30 * 6,
},
secret: process.env.NEXT_AUTH_SECRET,
callbacks: {
async jwt({ token, user, account, profile, isNewUser }) {
user && (token = { ...token, ...user });
return token;
},
async session({ session, token }) {
if (token) {
session.user = { ...userInfo };
}
return session;
},
},
});
Any help please?
Whenever a user logins in successfully, this is a sample of the server response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6ImFjY2VzcyJ9.eyJpYXQiOjE2NDY1MDc5ODEsImV4cCI6MTY0NzExMjc4MSwiYXVkIjoiaHR0cHM6Ly95b3VyZG9tYWluLmNvbSIsImlzcyI6ImZlYXRoZXJzIiwic3ViIjoiMiIsImp0aSI6ImVlMTExOGNhLTVhOGEtNGJiMC1iMDRkLTdlMTUzM2RlMmQ4YyJ9.T5gBB9CYulofSa_rTKP23wNG5YUMyEKtqQIFG0X5RX4",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6ImFjY2VzcyJ9.eyJ0b2tlblR5cGUiOiJyZWZyZXNoIiwidXNlciI6eyJpZCI6MiwiZmlyc3QiOiJKYW5lIiwibGFzdCI6IkRvZSIsImVtYWlsIjoiaGVsbG8xQGZlYXRoZXJzanMuY29tIiwicGhvbmUiOiIwNzk1ODU3MTM5IiwibG9jYXRpb24iOiJOYWlyb2JpIiwiaXNWZXJpZmllZCI6MCwiaXNBZG1pbiI6MCwicm9sZXMiOiJ1c2VyIiwiY29uZmlybWVkRW1haWwiOjEsImNvbmZpcm1lZFBob25lIjoxLCJjb25maXJtYXRpb25JZCI6IiIsImRlYWN0aXZhdGVkIjowLCJsb2dpbnMiOjAsImJpbyI6IiIsIndlYnNpdGUiOiIiLCJzb2NpYWwiOiIiLCJ1cGxvYWRJZCI6bnVsbCwiY3JlYXRlZEF0IjoiMjAyMi0wMy0wNVQxNzo0NDowNS4wMDBaIiwidXBkYXRlZEF0IjoiMjAyMi0wMy0wNVQxNzo0NDowNS4wMDBaIn0sImlhdCI6MTY0NjUwNzk4MSwiZXhwIjoxNjQ3MTEyNzgxLCJhdWQiOiJodHRwczovL3lvdXJkb21haW4uY29tIiwiaXNzIjoiZmVhdGhlcnMiLCJzdWIiOiIyIiwianRpIjoiNzJjMzZjMWQtZTQwYy00NTE4LTg2NDctYjczNDdiNWFjMzMxIn0.Ba_LBdDBYf5rcTWcR6AaR_uAyqHxpHg3rbqEii8h78I",
"authentication": {
"strategy": "local"
},
"user": {
"id": 2,
"first": "Jane",
"last": "Doe",
"email": "hello1#feathersjs.com",
"isAdmin": 0,
"roles": "user",
"createdAt": "2022-03-05T17:44:05.000Z",
"updatedAt": "2022-03-05T17:44:05.000Z"
}
}
Once the feathersjs accessToken expires, the site hits the api with the refreshToken, and gets a new one without the user's intervention.
I can't however include the refreshToken as there in no way to pass it into callback options of the [...nextauth].js file. How do I go about this?
EDIT: I managed to pass the refresh token, together with the accessToken like so:
// [...nextauth.js]
const providers = [
CredentialsProvider({
name: "Credentials",
authorize: async (credentials) => {
const { email, password, refreshToken = null } = credentials;
try {
if (refreshToken) {
console.log("REFRESHING TOKEN");
const { data } = await axios.post(
API_AUTH,
{
strategy: "local",
action: "refresh",
refresh_token: refreshToken,
},
{
headers: {
accept: "*/*",
"Content-Type": "application/json",
},
}
);
const token = JSON.stringify({
token: data.accessToken,
refresh: data.refreshToken,
});
const user = {
name: data.user.first,
email: data.user.email,
image: "/hkgghlk",
token,
};
if (user) {
return user;
}
} else {
console.log("LOGGING IN");
const { data } = await axios.post(
API_AUTH,
{ strategy: "local", email, password },
{
headers: {
accept: "*/*",
"Content-Type": "application/json",
},
}
);
const token = JSON.stringify({
token: data.accessToken,
refresh: data.refreshToken,
});
const user = {
name: data.user.first,
email: data.user.email,
image: "/hkgghlk",
token,
};
if (user) {
return user;
}
}
} catch (e) {
const errorMessage = e.response.data.message;
throw new Error(errorMessage);
}
},
}),
];
const callbacks = {
async jwt({ token, user, account, profile, isNewUser }) {
if (user) {
const { token: access, refresh } = JSON.parse(user.token);
token.accessToken = access;
token.refreshToken = refresh;
}
return token;
},
async session({ session, token }) {
session.refreshToken = token.refreshToken;
return session;
},
};
Now I know this is a hack. Is there a 'proper' way of doing this? I don't want the refreshToken saved in the session as this is not ideal.
If interested in how feathersjs creates a refresh token, here is how.
I've working in a project with Next.js (11.1.2) + NextAuth (^4.0.5) + Strapi(3.6.8).
I'm using Next Auth credentials provider and it's working fine. But I need to access a few user information using session, so I tried to do this using jwt and session callbacks.
When I log response from strapi inside authorize(), I receive { jwt:{}, user:{} }, so it's ok.
//[...nextauth.js]
async authorize(credentials, req) {
try {
const { data } = await axios.post(process.env.CREDENTIALS_AUTH_URL, credentials)
if (data) {
//console.log('data: ', data) //this is ok
return data;
}
else {
return null;
}
} catch (e) {
return null;
}
},
But, in jwt callback, when I log token, i'm getting a bizarre obj with {token:{token:{token:{...}}}:
// [...nextauth.js] callback:{ jwt: async (token) => { console.log(token) }}
token: {
token: {
token: {},
user: {
jwt: ...,
user: [Object]
},
account: { type: 'credentials', provider: 'credentials' },
isNewUser: false,
iat: ...,
exp: ...,
jti: ...
}
}
And account and user is always undefined inside that callbacks.
Finally, when I get session from useSession in a page, I get this:
// console.log(session) in any page
{
session: {
expires: "2022-01-12T19:27:53.429Z"
user: {} // empty
},
token:{
token:{
account: {type: 'credentials', provider: 'credentials'}
exp: ...
iat: ...
isNewUser: false
jti: "..."
token: {} // empty
user: { //exactly strapi response
jwt:{...}
user:{...}
}
}
}
}
All examples I've found aren't handling this objects with this confuse structure and I don't know if I missing something out. Can you help me?
This is my [...nextauth].js:
import NextAuth from "next-auth"
import CredentialsProvider from 'next-auth/providers/credentials'
import axios from 'axios';
export default NextAuth({
providers: [
CredentialsProvider({
name: '...',
credentials: {
email: {label: "Email", type: "text", placeholder: "email#provider.com"},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
try {
const { data } = await axios.post(process.env.CREDENTIALS_AUTH_URL, credentials)
if (data) {
//console.log('data: ', data)
return data;
}
else {
return null;
}
} catch (e) {
return null;
}
},
})
],
secret: process.env.SECRET,
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60 // 30 days
},
jwt: {
secret: process.env.JWT_SECRET,
encryption: true,
},
callbacks: {
jwt: async (token, account) => {
console.log('### JWT CALLBACK ###')
console.log('token: ', token)
console.log('account: ', account)
return token;
},
session: async (session, token, user) => {
console.log('### SESSION CALLBACK ###')
console.log('session: ', session)
console.log('user: ', token)
console.log('user: ', user)
return session;
}
},
pages: {
signIn: '/signin',
signOut: '/signin',
error: '/signin'
}
})
try pls:
- session(session, tokenOrUser)
+ session({ session, token, user })
- jwt(token, user, account, OAuthProfile, isNewUser)
+ jwt({ token, user, account, profile, isNewUser })
https://next-auth.js.org/getting-started/upgrade-v4#callbacks
I implemented this in next-auth following some tutorial online
import NextAuth from "next-auth"
import Providers from "next-auth/providers";
const https = require('https');
export default NextAuth({
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
const url = 'https://localhost/auth';
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
});
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify(credentials),
agent: httpsAgent,
headers: {
"Content-Type": "application/json"
}
})
const user = await res.json();
if (res.ok && user) {
return user;
} else {
return null;
}
}
}),
// ...add more providers here
],
callbacks: {
async jwt(token, user, account, profile, isNewUser) {
if (user?.type) {
token.status = user.type
}
if (user?.username) {
token.username = user.username;
}
return token
},
async session(session, token) {
session.type = token.type;
session.username = token.username;
return session
}
}
})
pretty standard. https://localhost/auth return an object like this (I called it user for now)
{
token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MzY0MTE4NjEsImV4cCI6MTYzNjQxNTQ2MSwicm9sZXMiOlsiUk9MRV9VU0VSIl0sInVzZXJuYW1lIjoiZXJuYTM5QHdlYmVyLmNvbSJ9.Abenx1GhB-_d9LVpLfa2NYp62Lbw6U65EUQowA0jA_aykx1m-BlBR_YBcL4XIJsknJ99NN8Ees4Zxdsphfhjs7du4TR2MgTITHYy-BYjBX9CsluVSBpm-L7c-oK5vu70eumAy1ixy5MKOTN2EQYCm65RszSheIwZ4LN8vSuzxzZuLszRG9nbpauiHDpYCeLrNeNkz4lhTicfWkdPafR8vhqt4MIeCl-kxbMqc35UNmglzE7n-b9zVh4OhU7bSCoPKZySL5c4GSf7UFFD-mXIe6s9b4qYSXJuLpdspFJSgP7UoEGP1gh8fTb5MDZREYyZOpK3BMU8EdwokngVR9zrbw'
}
I would like to know how to store this token to be used in further calls to my API. I can see the token object in the session callback is
{ iat: 1636411862, exp: 1639003862 }
so next-aut is not doing this for me. Should I set an httpOnly cookie in the session callback? or right after
if (res.ok && user) {
just before to return user?
I found a way just updating the callbacks:
callbacks: {
async jwt(token, user, account, profile, isNewUser) {
if (user?.token) {
token.token = user.token;
}
return token;
},
async session(session, token) {
return session;
}
}
in this way the token from the API is now stored in a httpOnly cookie called __Secure-next-auth.session-token (assuming the token from the API is in the format like above).
If you store the JWT in the cookies so every time you're calling your API you could check the cookie header to see if you have it.