I have token.ts which looks like this -
export const contentTypeHeaderWithToken = {
'headers': {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
}
};
In my reduxtoolkit, I am using this contentTypeHeaderWithToken like this -
export const fetchAllRoles = createAsyncThunk<IGetRolesResponse>(
'user/getroles',
async (user, {rejectWithValue}) => {
try {
const response = await Services.get('/roles', contentTypeHeaderWithToken);
return response.data.slice().reverse() as IGetRolesResponse;
} catch(err) {
const error = err as any;
return rejectWithValue(error?.response?.data)
}
},
);
In try statement above, contentTypeHeaderWithToken is coming out to be null for the very first time.
Before pulling the token value, I have verified, the token is present in the browser.
Any idea what I am missing ?
Related
I created an Axios instance to set up the baseURL and the headers. The header also needs to contain the token for authorization.
export const instance = axios.create({
baseURL: import.meta.env.VITE_API_URL,
headers: {
Authorization: `Bearer ${localStorage.getItem(LOCAL_STORAGE_API_KEY)}`
},
validateStatus: () => true
});
when the user logs in, I call an API to get some data related to the user using useQuery. When I log in, I try to store the token in local storage, but I think I'm doing something wrong and I get an error from the backend.
export const LOCAL_STORAGE_API_KEY = 'token';
import { instance } from './ApiProvider';
import { LOCAL_STORAGE_API_KEY } from '#/helpers/constants';
export const loginActions = async ({ email, password }) => {
const response = instance
.post('/api/v1/Auth/Login', {
user: {
email: email,
password: password
}
})
.then((data) => {
instance.defaults.headers.post[
'Authorization'
] = `Bearer ${localStorage.getItem('LOCAL_STORAGE_API_KEY')}`;
return data;
});
return response;
};
The problem is that instance is created before you have the auth header value available and hence on subsequent call it will pass the value as undefined.
You can use axios interceptors for this task.
instance.interceptors.request.use(
function(config) {
const token = localStorage.getItem("LOCAL_STORAGE_API_KEY");
if (token) {
config.headers["Authorization"] = 'Bearer ' + token;
}
return config;
},
function(error) {
return Promise.reject(error);
}
);
I'm using redux toolkit and i want to send the token to verify if the user is logged in or not so he can like a post.
My code is as follow :
const userInfo = localStorage.getItem('userInfo') ?
JSON.parse(localStorage.getItem('userInfo')) : null
const config = {
headers: {
Accept: "application/json",
'Content-type': 'application/json',
}
}
const auth = {
headers: {
Authorization: `Bearer ${userInfo?.token}`
}
}
export const likePost = createAsyncThunk("posts/likePost",
async (id,{ rejectWithValue }) => {
try {
const { data } = await axios.patch(`http://localhost:5000/posts/like/${id}/`,
config,
auth,
id,
)
console.log(userInfo?.token)
return data
} catch (error) {
return rejectWithValue(error.response.data)
}
}
)
when i check the headers i find that it takes the token of the previous logged in person
I am building a login page with Mobx, MUI V5, react-router V6 and react-hook-form.
My first API call is to authenticate the application, apiAuth() will return a token that needs to be passed to all subsequent API calls.
On the next call, userAuth(), I try to validate the user credential.
As you can see, the method takes 3 arguments (a token, card number and, password)
When the user credentials are valid, I can login successfully.
When the user credentials are not valid on the first try, it works as
expected. I receive 400 (Bad Request) error from the API and display the error message on the
interface.
That said when I entered the user credentials once more, I get a 401 (Unauthorized) error.
Upon further inspection of the request headers, when I compared the authorization header in both userAuth() calls, I see that the token's value on the second call was concatenated with the previous token
Any ideas as to why for this behavior?
My AuthStore looks as follow:
class AuthStore {
isAuth = false
isAuthFail = false
AuthFailObj = {}
bearerToken = ''
cardNum = ''
password=''
constructor() {
makeObservable(this, {
isAuth: observable,
AuthFailObj: observable,
isAuthFail:observable,
bearerToken: observable,
cardNum: observable,
password: observable,
auth: action,
setIsAuth: action,
setToken: action,
setCardNum: action,
setPassword: action,
setIsAuthFail: action,
setAuthFailObj: action
})
}
setIsAuth = isAuth => {
this.isAuth = isAuth
}
setToken = bearerToken => {
this.bearerToken = bearerToken
}
setCardNum = cardNum => {
this.cardNum = cardNum
}
setPassword = password => {
this.password = password
}
setIsAuthFail = b => {
this.isAuthFail = b
}
setAuthFailObj = ojb => {
this.AuthFailObj = ojb
}
auth = async () => {
const apiRes = await apiAuth()
if (apiRes.status === 200){
const apiData = await apiRes.text()
this.setToken(JSON.parse(apiData)[0].token)
}
const userAuthRes = await userAuth(this.bearerToken, this.password, this.cardNum)
if (!userAuthRes.ok){
this.setIsAuthFail(true)
const errRes = await userAuthRes.text()
userAuthRes.status === 400 && this.setAuthFailObj(JSON.parse(errRes))
userAuthRes.status === 401 && this.setAuthFailObj('401 (Unauthorized)')
}
if (userAuthRes.ok){
const userAuthData = await userAuthRes.text()
userStore.updateUserProfile(JSON.parse(userAuthData))
this.setIsAuth(true)
}
}
}
export default new AuthStore()
In the login form, the submit method looks like this:
const submit = async (data) => {
AuthStore.setCardNum(data.Card_Number)
AuthStore.setPassword(data.Password)
setToggle(true)
await AuthStore.auth()
if (AuthStore.isAuth) {
navigate('/dashboard')
} else {
// clear form
}
}
Finally, the PrivateRoute logic reads is simple:
const PrivateRoute = () => {
return AuthStore.isAuth ? <Outlet /> : <Navigate to='/' />
}
The function userAuth()
const myHeaders = new window.Headers()
const { REACT_APP_API_ACC_MNG_AUTH_URL } = process.env
const userAuth = async (bearerToken, password, cardNum) => {
myHeaders.append('Authorization', `Bearer ${bearerToken}`)
myHeaders.append('Content-Type', 'application/json')
const raw = JSON.stringify({
cardNumber: cardNum,
pinNumber: password
})
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
}
const response = await window.fetch(REACT_APP_API_ACC_MNG_AUTH_URL, requestOptions)
return response
}
The issue is that you're using the Headers API and appending to the headers instead of setting them, which exist outside the function scope and are updated. From MDN:
The append() method of the Headers interface appends a new value onto an existing header inside a Headers object, or adds the header if it does not already exist.
So every time you make a request, if you append the header, it will be added on to the existing value. You could move your headers declaration inside of the function, and create a new object each time you make a request:
const { REACT_APP_API_ACC_MNG_AUTH_URL } = process.env
const userAuth = async (bearerToken, password, cardNum) => {
const myHeaders = new window.Headers()
myHeaders.append('Authorization', `Bearer ${bearerToken}`)
myHeaders.append('Content-Type', 'application/json')
const raw = JSON.stringify({
cardNumber: cardNum,
pinNumber: password
})
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
}
const response = await window.fetch(REACT_APP_API_ACC_MNG_AUTH_URL, requestOptions)
return response
}
Or you could just pass them in as an object, which is allowed by the Fetch API:
const userAuth = async (bearerToken, password, cardNum) => {
const raw = JSON.stringify({
cardNumber: cardNum,
pinNumber: password
});
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow',
headers: {
'Authorization': `Bearer ${bearerToken}`,
'Content-Type': 'application/json',
}
};
const response = await window.fetch(REACT_APP_API_ACC_MNG_AUTH_URL, requestOptions);
return response;
}
I'm developping an API consuming web front site.
The problem
All my API saga were like this :
export function* login(action) {
const requestURL = "./api/auth/login"; // Endpoint URL
// Select the token if needed : const token = yield select(makeSelectToken());
const options = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + btoa(JSON.stringify({ login: action.email, password: action.password })),
}
};
try {
// The request helper from react-boilerplate
const user = yield call(request, requestURL, options);
yield put(loginActions.loginSuccess(user.token);
yield put(push('/'));
} catch (err) {
yield put(loginActions.loginFailure(err.detailedMessage));
yield put(executeErrorHandler(err.code, err.detailedMessage, err.key)); // Error handling
}
}
And I had the same pattern with all my sagas :
Select the token if I need to call a private function in the start of the saga
const token = yield select(makeSelectToken());
Handle errors on the catch part
export const executeErrorHandler = (code, detailedMessage, key) => ({
type: HTTP_ERROR_HANDLER, status: code, detailedMessage, key
});
export function* errorHandler(action) {
switch (action.status) {
case 400:
yield put(addError(action.key, action.detailedMessage));
break;
case 401:
put(push('/login'));
break;
//other errors...
}
}
export default function* httpError() {
yield takeLatest(HTTP_ERROR_HANDLER, errorHandler);
}
The solution I came up with
Remove the token parts and error handling part and puth them inside the call helper :
export function* login(action) {
const url = `${apiUrl.public}/signin`;
const body = JSON.stringify({
email: action.email,
password: action.password,
});
try {
const user = yield call(postRequest, { url, body });
yield put(loginSuccess(user.token, action.email));
yield put(push('/'));
} catch (err) {
yield put(loginFailure());
}
}
// post request just call the default request with a "post" method
export function postRequest({ url, headers, body, auth = null }) {
return request(url, 'post', headers, body, auth);
}
export default function request(url, method, headers, body, auth = null) {
const options = { method, headers, body };
return fetch(url, addHeader(options, auth)) // add header will add the token if auth == true
.then(checkStatus)
.then(parseJSON)
.catch(handleError); // the error handler
}
function handleError(error) {
if (error.code === 401) {
put(push('/login')); // <-- Here this doesn't work
}
if (error.code == 400) {
displayToast(error);
}
}
function addHeader(options = {}, auth) {
const newOptions = { ...options };
if (!options.headers) {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
...options.headers,
};
}
if (auth) {
const token = yield select(makeSelectToken()); // <-- here it doesn't work
newOptions.headers.Authorization = `Bearer ${auth}`;
}
return newOptions;
}
I know the solution is between generator functions, side effects, yield call / select but I tried so many things it didn't work. For example, if I wrap everything inside generator functions, the token load is executed after the code continues and call the API.
Your help would be appreciated.
You need to run any and all effects (e.g. yield select) from a generator function, so you'll need generators all the way down to the point in your call stack where you yield an effect. Given that I would try to push those calls as high as possible. I assume you may have getRequest, putRequest etc. in addition to postRequest so if you want to avoid duplicating the yield select you'll want to do it in request. I can't fully test your snippet but I believe this should work:
export function* postRequest({ url, headers, body, auth = null }) {
return yield call(request, url, 'post', headers, body, auth); // could yield directly but using `call` makes testing eaiser
}
export default function* request(url, method, headers, body, auth = null) {
const options = { method, headers, body };
const token = auth ? yield select(makeSelectToken()) : null;
try {
const response = yield call(fetch, url, addHeader(options, token));
const checkedResponse = checkStatus(response);
return parseJSON(checkedResponse);
} catch (e) {
const errorEffect = getErrorEffect(e); // replaces handleError
if (errorEffect) {
yield errorEffect;
}
}
}
function addHeader(options = {}, token) {
const newOptions = { ...options };
if (!options.headers) {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
...options.headers,
};
}
if (token) {
newOptions.headers.Authorization = `Bearer ${token}`;
}
return newOptions;
}
function getErrorEffect(error) {
if (error.code === 401) {
return put(push('/login')); // returns the effect for the `request` generator to yeild
}
if (error.code == 400) {
return displayToast(error); // assuming `displayToast` is an effect that can be yielded directly
}
}
I am storing JWT token in LocalStorage after Login success is dispatched and routing to next component. But the next component API call is not able to take LocalStored token.
If i refresh the page and click again, it is accepting the token. Dont know the issue.
This is Axios Instance and Login Dispatch respectively
const instance = axios.create({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8',
'x-access-token': localStorage.getItem('accessToken'),
},
withCredentials: true,
validateStatus: (status) => status === 200
});
export function checkLogin(data, history) {
return function (dispatch) {
return dispatch(makeAPIRequest(loginAPI, data)).then(function (response) {
if (response.data.success == 1) {
localStorage.removeItem('accessToken')
localStorage.setItem('accessToken', response.data.data.token)
dispatch({ type: STORE_SESSION_TOKEN, authenticated: response.data.data.auth, token: response.data.data.token,userDetails: response.data.data.user });
history.push('/dashboard')
}
})
}
}
Expecting to take token from Localstorage from very next call from Dashboard. But that doesn't happen. says no token and redirects to Login
I think the problem is when you create the axios instance, the token is not available in localStorage.
You should try the default headers of axios:
export const AUTHORIZATION = 'authorization'
export const API = axios.create({
baseURL: `http://localhost:3000/api`
})
export const authorize = (token) => {
if (token) {
API.defaults.headers.Authorization = `Bearer ${token}`
localStorage.setItem(AUTHORIZATION, token)
}
}
export const unauthorize = () => {
delete API.defaults.headers.Authorization
localStorage.removeItem(AUTHORIZATION)
}
authorize(localStorage.getItem(AUTHORIZATION))
When you receive the token from the API in your actions, you have to save it to localStorage.
You need to get your header dynamically, because the token is not there while creating the instance and it might change later:
const JSON_HEADERS = {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8'
};
const getTokenFromStorage = () => localStorage.getItem('token');
const getHeaders = () => getTokenFromStorage().then((token) => {
if (!token) {
return JSON_HEADERS;
}
return {
... JSON_HEADERS,
'x-access-token': token
};
});
export const makeAPIRequest = ({ method, url, data }) => {
return axios({
method,
url,
data,
headers: getHeaders()
});
}