Where to store JWT token from an API in next-auth - javascript

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.

Related

In Next-Auth is possible use different providers but with the same e-mail?

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];
}

Next auth session disappearing data in development environment

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?

How to render data from parse in next-auth

I want to render data in client side below from parse after signin from next auth using useSession
{
objectId: 'nttMcKIOQJ',
username: 'fiik346',
email: 'fiik346#gmail.com',
createdAt: '2022-06-26T07:56:41.888Z',
updatedAt: '2022-06-26T07:56:41.888Z',
ACL: { '*': { read: true }, nttMcKIOQJ: { read: true, write: true } },
sessionToken: 'r:2f2c569be3b5120f3893834c2ace7b67'
}
But it's just render email
{"user": {"email":"fiik346#gmail.com"},"expires":"2022-07-26T09:01:41.163Z"}
"authenticated"
This is my callbacks code
// /pages/api/auth/[...nextauth].js
...
callbacks: {
async session({ session, token, user }) { // Send properties to the client, like an access_token from a provider. return session
}, async jwt({ token, user, account, profile, isNewUser }) { return token
} },
...
And client side code
import { useSession } from 'next-auth/react'
import { getToken } from 'next-auth/jwt'
export default function accountIndex() {
const {data: session, status} = useSession() return ( <div>
<h1>Account is {status}</h1>
<pre className="overflow-auto">
{JSON.stringify(session)}
<br/>
{JSON.stringify(status)}
</pre>
</div>
)
}
I don't know how to change my callbacks code to display data from database
callbacks: {
jwt: async ({token, user}) => {
if (user) {
token.data = user
}
return token
},
session: async ({session, token}) => {
if (token.data) {
session.user = token.data
}
return session
}
}
After a while and try harder, I was successful to render data. Just change callback code like below.
...
callbacks: {
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.accessToken = token.accessToken
session.user = token.data
return session
},
async jwt({ token, user, account, profile, isNewUser }) {
if (user) {
token.data = user
}
return token
}
},
...

Problem using Next-Auth with Credentials Provider for authenticating on existing system

I am using Next-Auth Credentials provider to authenticate using our existing API.
When I follow the directions on https://next-auth.js.org/configuration/callbacks
like this:
callbacks: {
async jwt({ token, user }) {
if (user) {
token.accessToken = user.jwt
}
return token
},
async session({ session, token, user }) {
session.accessToken = token.accessToken
return session
}
}
the resulting session object from useSession() looks like this:
{
expires: "2022-03-22T18:29:02.799Z",
user: {email: 'john#nextIsGreat.com'}
}
I can't use that as it does not have the token available.
So I was able to make up my own working solution, but it is kind of strange because of the way things are grouped together. Here is what I am doing now, that I am trying to figure out how to do better. I use comments to point out the problem areas:
[...nextauth].js:
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import axios from 'axios'
export default NextAuth({
providers: [
Credentials({
name: 'Email and Password',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' }
},
authorize: async (credentials) => {
const url = process.env.API_URL + '/authenticate'
const result = await axios.post(url, {
username: credentials.username,
password: credentials.password
})
const user = result.data
console.log(user)
//It logs this:
/*
{
jwt: 'eyJhbasU1OTJ9.NQ356H4Odya62KmN...', //<---***This is the token i pass in to all of my API calls****
user: {
userId: 207,
email: 'john#nextIsGreat.com',
firstName: 'John',
lastName: 'Doe',
roleId: 1,
}
}
*/
if (user) {
return Promise.resolve(user)
} else {
return Promise.resolve(null)
}
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
if (user.jwt) {
token = { accessToken: user.jwt, restOfUser: user.user }
}
}
return token
},
async session(seshProps) {
return seshProps
}
}
})
Home.js:
export const Home = () => {
const { data: session } = useSession()
console.log(session)
//LOGS THIS --->
/*
{
"session": { "user":{}, "expires":"2022-03-22T17:06:26.937Z"},
"token":{
"accessToken":"eyJ...",
"iat":1645376785,
"exp":1647968785,
"jti":"41636a35-7b9a-42fd-8ded-d3dfgh123455a"
"restOfUser": {
"userId":207,
"email":"john#nextIsGreat.com",
"firstName":"John",
"lastName":"Doe",
"roleId":1
}
}
{
*/
const getPosts=()=> {
const url = 'localhost:4000/posts'
const {data} = axios.get(url, {
Authorization: session.token.accessToken <--**This is the way I am calling my API
})
console.log(data)
}
return (
<div onClick={getPosts}>
Hello, {session.token.restOfUser.firstName}
/* I have to access it like this now, which seems wrong ***** */
</div>
)
}
Cheers for creating your own solution but you do not need it. NextAuth CredentialsProvider handles it already by setting your NextAuth session configuration to session: {strategy: "jwt", ... }.
You can also remove your callbacks for jwt() and session() and remove your owned generated JWT access token. As you do not need it, this way you can authenticate your existing system.
And at your CredentialsProvider({authorize(){}} authorize method. If you had directly connected to the user database, you can directly look up the user credential without doing a post request since it is already considered a server-side function.

Next-auth custom auth provider with custom backend

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. 🙂

Categories

Resources