How to refresh feathers.js token with next-auth and next.js setup - javascript

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.

Related

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?

Error response graph.microsoft.com/v1.0/me/manager

Look at my code, what am I doing wrong? I need to get https://graph.microsoft.com/v1.0/users/${uniqueName}/manager. But the request fails
But when I try to execute the https://graph.microsoft.com/v1.0/${uniqueName} query, everything goes well. What to fix so that the https://graph.microsoft.com/v1.0/users/${uniqueName}/manager request is successful?
fastify.post('/login', {
preHandler: (request, _, next) => {
if (!request.body || !request.body.username || !request.body.password) {
const error = new Error('Credentials are missing');
error.statusCode = 400;
return next(error);
}
return next();
},
}, async (request, reply) => {
const { username, password } = request.body;
const userData = await fastify.helpers.authentication.getUserTokens(username, password, azureConfig.CLIENT_SCOPE);
await replyWithTokens(fastify, reply, userData);
});
And next
const getUserTokens = async (username, password, scope) => {
const authUrl = `https://login.microsoftonline.com/${azureConfig.TENANT_NAME}/oauth2/v2.0/token`;
const body = {
client_id: azureConfig.CLIENT_ID,
client_secret: azureConfig.CLIENT_SECRET,
grant_type: 'password',
password,
scope,
username,
};
const authResponse = await fetch(authUrl, {
body: new URLSearchParams(body).toString(),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'POST',
});
if (!authResponse.ok) {
fastify.helpers.error.throwError(422, 'Authentication failed');
}
const result = await authResponse.json();
const decodedData = jwt.decode(result.access_token);
const uniqueName = String(decodedData.unique_name || '').toLowerCase();
const name = String(decodedData.upn || uniqueName).toLowerCase();
const agentAttributes = {};
if (!uniqueName) {
fastify.helpers.error.throwError(400, 'Unique name not found');
}
let recheckSan = true;
let san = name.split('#').shift();
let agent = await fastify.db.models.Agent.findOne({
where: { uniqueName },
});
let radId = '';
const graphAuthResponse = await fetch(authUrl, {
body: new URLSearchParams({
...body,
scope: 'email openid profile https://graph.microsoft.com/User.Read',
}).toString(),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'POST',
});
if (graphAuthResponse.ok) {
const graphAuthResult = await graphAuthResponse.json();
const { access_token: graphAccessToken = '' } = graphAuthResult;
// eslint-disable-next-line max-len
const graphResponse = await fetch(`https://graph.microsoft.com/v1.0/users/${uniqueName}/manager`, {
headers: {
authorization: `Bearer ${graphAccessToken}`,
'content-type': 'application/json',
},
});
if (graphResponse.ok) {
const graphResult = await graphResponse.json();
console.log(graphResult)
}
}
}
I want to receive such response
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects/$entity",
"#odata.type": "#microsoft.graph.user",
"id": "1111112-68cf-4896-b2d0-13b5c6264113",
"businessPhones": [
"111 111 11111"
],
"displayName": "Wer, John",
"givenName": "John",
"jobTitle": "SENIOR DEVELOPMENT ARCHITECT",
"mail": "somemail#mail.com",
"mobilePhone": "111 111 1111",
"officeLocation": "Atlanta",
"preferredLanguage": null,
"surname": "John",
"userPrincipalName": "somemail#mail.com"
}
But I get such an error response. What am I doing wrong? Thanks!
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"date": "2022-01-14T20:40:30",
"request-id": "9e2b5937-4bd0-4fdb-a1ae-1b22cef09772",
"client-request-id": "9e2b5937-4bd0-4fdb-a1ae-1b22cef09772"
}
}
}
To get the managers of other users in your organization on your behalf, you need to have User.Read.All delegated permission
Once the permission is assigned, admin consent needs to be granted for the same.
Then you would be able to fetch the managers info of other users in your organization
You can test the same in Graph Explorer first. If it is successful, you can make the changes accordingly in your JavaScript code

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

NextAuth: JWT callback returning object

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

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

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.

Categories

Resources