I have pages with connexion and deconnexion to login on my nuxt app.
I use Symfony for the back and the librairy JWT for token authentification.
Here this is the code auth.js called when an user is login on my app. This works but I would like to store in my vuex the data into the token (I have the role and I would like to use it to display different things on page in function of the role). How can I do this ? Thanks for help
import axios from "axios";
import jwtDecode from "jwt-decode";
export const login = async (data) => {
const token = await axios
.post(process.env.baseUrl + 'login_check', data)
.then (response => response.data.token )
window.localStorage.setItem("authToken", token)
axios.defaults.headers["Authorization"] = "Bearer " + token;
}
export const logout = () => {
window.localStorage.removeItem("authToken")
delete axios.defaults.headers['Authorization'];
}
export const verifToken = () => {
const token = window.localStorage.getItem("authToken");
if (token ) {
const jwtData = jwtDecode(token);
if (jwtData.exp * 1000 > new Date().getTime()) //
{
axios.defaults.headers["Authorization"] = "Bearer " + token;
//console.log("connexion axios OK")
return true
}
else // if expired
{
logout();
}
}
//if no token
else {
logout();
console.log("no token")
}
}
Related
I recently upgraded axios in one of my project (from 0.27 to 1.1.3) and the interceptor I created to refresh the user's access token doesn't work anymore, u can find in the screenshot bellow the error I'm having. I searched online but can't find anything working.
To precise, whenever the user's access token expires, my back end send the 401 error and so the interceptor is called. The returned token is good as well as the setting to the headers.
Thank you in advance for your time.
import axios from "axios";
import router from "#/router";
import store from "#/store/index";
const instance = axios.create({
baseURL: "http://localhost:3000",
});
instance.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;
console.log("error:", error);
if (
error.config.url != "users/refreshToken" &&
error.response.status === 401 &&
!originalRequest._retry
) {
originalRequest._retry = true;
await instance
.get("users/refreshToken", { withCredentials: true })
.then((response) => {
const token = response.data.accessToken;
console.log("token:", token);
store.state.token = token;
instance.defaults.headers.common["authorization"] = `Bearer ${token}`;
originalRequest.headers["authorization"] = `Bearer ${token}`;
localStorage.setItem("token", token);
})
.catch(() => {
store.commit("logout");
localStorage.removeItem("token");
router.push({ name: "login", params: { error: "refreshToken" } });
});
return instance(originalRequest);
}
return Promise.reject(error);
}
);
export default instance;
The error :
I need to get the data from the custom signIn page in order to write a user to the sanity database. But these signIn data is only obtained in [...nextauth].js file.
Code:
[...nextauth].js
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
export default NextAuth({
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: "xxxxxx",
clientSecret: "xxxxx",
}),
// ...add more providers here
],
secret: "something",
pages: {
signIn: '/auth/signin',
},
callbacks: {
async session({ session, token, user }) {
session.user.username = session.user.name
.split(' ')
.join('')
.toLocaleLowerCase()
session.user.uid = token.sub
return session
},
},
})
And these session data can be used inside components using useSession from next-auth.
But while trying to get the data to my custom signIn page, session is undefined.
import { getProviders, signIn as signIntoProvider } from "next-auth/react";
import { sanityClient } from "../../sanity";
import { useSession } from 'next-auth/react';
function signIn({users}) {
const { data: session } = useSession()
const onSubmit = (data) => {
fetch("/api/createUser", {
method: "POST",
body: JSON.stringify(data),
})
.then((resp) => {
console.log(resp);
})
.catch((err) => {
console.log(err);
});
};
const checkIfUserThere = async () => {
let newUser = session.user.email;
for (const data of users) {
if (data.email === newUser) {
return true;
}
}
return false;
};
useEffect(() => {
(async () => {
const userExists = await checkIfUserThere();
if(userExists === false ){
onSubmit(session.user); //write to sanity database
}
})();
}, []);
return (
<>
<button
className="rounded-lg bg-blue-500 p-3 text-white"
onClick={() =>
signIntoProvider("google", { callbackUrl: "/" })
}
>
Sign in with Google
</button>
</>
);
}
The above code is for a custom signIn page.
What is expected :
Once the user clicks the sign-in with the Google button, the session data must be added to the sanity database. But in my case, session here is undefined.
A simple way to do this is to write the logic inside the [...nextAuth].js file.
To solve the task of popularizing a document in Sanity from a Google authentication, you must first establish a connection to your Sanity project. Note that this import comes from the 'npm i #sanity/client' package or 'yarn add #sanity/client' and is not a reference to the configuration located in the sanity.js file. To do this, you can import the #sanity/client library and set up a configuration to connect to your project:
import sanityClient from "#sanity/client";
const config = {
dataset: "DATASET_NAME",
projectId: "PROJECT_ID",
useCdn: 'CDN'
token: "YOUR_TOKEN_SANITY",
};
export const client = sanityClient(config);
After setting up authentication with Google, you must set up a callback to run every time a user authenticates. This callback should look in Sanity to see if the user already exists, and if not, create a new user document in Sanity with the authenticated user's information:
const populateSanityUser = async (user) => {
const sanityUser = await client.fetch(
`*[_type == "users" && email == "${user.email}"]{ //check if the email exists
email
}`
);
if (sanityUser.length > 0) { //if exists
return sanityUser;
} else { //if not, create a new user with Google user session data
try {
await client.create({
_type: "user",
name: user.name,
email: user.email,
urlImage: user.image,
... // another field in your document
});
return user;
} catch (error) {
return error;
}
}
};
export default NextAuth({
...authOptions,
callbacks: {
async signIn(user) {
const isAllowedToSignIn = true; //optional
if (isAllowedToSignIn) {
const sanityUser = await populateSanityUser(user.user);
return sanityUser;
} else {
return false;
}
},
},
});
Important: Make sure that when you pass the user coming from NextAuth callback function you use user.user, as it comes nested with more data.
More information about callbacks in NextAuth here: https://next-auth.js.org/configuration/callbacks
I have a function which generates a refresh token.
async refreshToken(token: string): Promise<{ token: string } | ErrorDetails> {
if (!token)
throw new HttpException('No token provided', HttpStatus.BAD_REQUEST)
token = token.split(' ')[1]
const jwtToken = await this.jwtService
.verifyAsync(token, {
secret: process.env.JWT_SECRET,
})
.catch((error) => {
if (error === 'jwt expired') return
})
// Here if token is expired jwtToken is null
if (!jwtToken)
throw new HttpException('Invalid token', HttpStatus.FORBIDDEN)
const user = await this.userService.findUserByAllFields(jwtToken.user)
if (!user) throw new HttpException('Invalid token', HttpStatus.FORBIDDEN)
const userDetails = this.userService._getUserDetails(jwtToken.user)
const jwt = await this.jwtService.signAsync({ userDetails })
return { token: jwt }
}
I wan't to check if the token is right but without checking the expiration
Is there any way to handle it ?
this.jwtService.verify(token, {
secret,
ignoreExpiration: true,
});
If you don't pass expireIn option or exp claim there will not be any exp claim, so the JWT does not have any expiration.
I made a React app and I making requests to the backend using Axios. I created a middleware in my backend for authorization and on the frontend side I'm trying to pass to every call that is made to the backend the auth token if exists in the localStorage. Before I added the logic for that everything worked perfectly, now every time I try to log in or register I get this in the console
TypeError: Cannot read properties of undefined (reading 'cancelToken')
at throwIfCancellationRequested (dispatchRequest.js:12:1)
at dispatchRequest (dispatchRequest.js:24:1)
at async auth.js:6:1
My index.js which handles every call to the backend looks like this:
import axios from 'axios';
const API = axios.create({
baseURL: 'http://localhost:3500'
})
API.interceptors.request.use((req) => {
if (localStorage.getItem('profile')) {
req.headers.Authorization = `Bearer ${JSON.parse(localStorage.getItem('profile')).token}`
}
})
export const fetchHunts = () => API.get('/hunts');
export const createHunt = (newHunt) => API.post('/hunts', newHunt);
export const updateHunt = (id, updatedHunt) => API.patch(`/hunts/${id}`, updatedHunt);
export const deleteHunt = (id) => API.delete(`/hunts/${id}`);
export const signInAdmin = (formData) => API.post('/admins/signinadmin', formData);
export const signUpAdmin = (formData) => API.post('/admins/signupadmin', formData);
Right now I am not logged in so there is no profile in the localStorage. I tried to add this, I found this here on stack overflow but didn't work
const CancelToken = Axios.CancelToken;
instance.interceptors.request.use(req => {
/* some logic */
const CancelToken = Axios.CancelToken;
return {
...req,
cancelToken: new CancelToken((cancel) => cancel('Cancel repeated request'))
};
});
but when I used this it only returned " Cancel repeated request " and did nothing. Do you know how can I fix that? Thank you in advance!
Based on the Axios Documentation - Interceptors, the interceptor function should return the req.
API.interceptors.request.use((req) => {
if(localStorage.getItem('profile')) {
req.headers.Authorization = `Bearer ${JSON.parse(localStorage.getItem('profile')).token}`;
}
return req;
})
My 2cents:
looks like it's easier NOT to make the request in the first place, if user is not authorized =)
Just add a global middleware on frontend to redirect user to auth page.
Here is an example in Vue, but you get the logic.
import { Middleware } from '#nuxt/types';
import { RoutesName } from '~/shared/repository/routes/routes-name';
const auth: Middleware = async (context) => {
const { route, store } = context;
const isAuthorized = store.getters['user/isAuthorized'];
const isAuthPage = route.name === RoutesName.auth;
if (!isAuthorized && !isAuthPage) {
return context.redirect(`/${RoutesName.auth}`);
}
if (isAuthorized && isAuthPage) {
return context.redirect('/');
}
};
export default auth;
I am using axios interceptors to check auth token on every request. This works fine. But when accessToken is null getToken() is fired twice. Is there a way to wait for the getToken to finish ? I just want getToken to fire once. The other requests needing a token should wait until getToken is fulfilled.
let isAlreadyFetchingAccessTokenRequest = false;
api.interceptors.request.use(
async config => {
let token = window.localStorage.getItem("accessToken");
if (!isAlreadyFetchingAccessTokenRequest && !token) {
isAlreadyFetchingAccessTokenRequest = true;
token = await getToken();
console.log("1. save token to local storage", token);
window.localStorage.setItem("accessToken", token);
}
config.headers.Authorization = `Bearer ${token}`;
return config;
},
function(error) {
return Promise.reject(error);
}
);
You are able to await a promise multiple times.
So could try something like this
let tokenPromise = null;
api.interceptors.request.use(
async config => {
let token = window.localStorage.getItem("accessToken");
if (!token) {
if (!tokenPromise) {
tokenPromise = getToken();
}
token = await tokenPromise;
console.log("1. save token to local storage", token);
window.localStorage.setItem("accessToken", token);
}
config.headers.Authorization = `Bearer ${token}`;
...