Authorization headers taking the previous token value redux toolkit - reactjs

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

Related

Not getting the localstorage token in my react component

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 ?

how to solve problem with oAuth Zoom in Nextjs?

I am trying to authenticate the user in order to get data to use to create or update meetings later. but it full of errors.
Here I am sending Post Requests in order to get the AccessToken and then get the UserData as props.
export async function getServerSideProps(res){
const oauth = async() => {
const zoomUserData = [];
const b = Buffer.from(process.env.ZOOM_API_KEY + ":" + process.env.ZOOM_API_SECRET);
const zoomRes = await fetch(`https://zoom.us/oauth/token?grant_type=authorization_code&code=${req.body.code}&redirect_uri=${process.env.ZOOM_REDIRECT_URL}`, {
method: "POST",
headers: {
Authorization: `Basic ${b.toString("base64")}`,
},
});
const zoomData = await zoomRes.json();
const zoomUserRes = await fetch("https://api.zoom.us/v2/users/me", {
method: "GET",
headers: {
Authorization: `Bearer ${zoomData.access_token}`,
},
});
const zoomUserData = await zoomUserRes.json();
/*
Encrypt and store below details to your database:
zoomUserData.email
zoomUserData.account_id
zoomData.access_token
zoomData.refresh_token
zoomData.expires_in // convert it to time by adding these seconds to current time
*/
}
return{
props:{zoomUserData}
}
}
and then i am passing the props to a page component like that :
export default function Meeting({zoomUserData}) {
const router = useRouter();
useEffect(() => {
if (router.query.code) {
fetch('/connectZoom',
{ method: 'POST',
headers: {
'ContType': 'application/json',
},
body: JSON.stringify({ code: router.query.code }),
}).then(() => {
console.log("success")
}).catch(() => {
console.log("No!")
});
}
}, [router.query.code]);
console.log(zoomUserData)
return (
<a href={`https://zoom.us/oauth/authorize?response_type=code&client_id=${process.env.ZOOM_API_KEY}&redirect_uri=${process.env.ZOOM_REDIRECT_URL}`}>
Connect Zoom
</a>
)
}

MOBX is concatenating an observable instead of updating it

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

React Apollo Link - How to forward operation after a promise has resolved?

So I figured out how to setup a middleware to handle my auth tokens, as well as getting new ones, if need be. The problem is that, there is an edge case here when, after the promise is resolved, the operation gets forwarded without the proper headers set, leading to another call that could potentially be unauthenticated. I feel the trick here is pretty straightforward, but I can't seem to figure it out. Is there a way to return the results from a promise back up to the enclosed function? I haven't found much luck regarding this, but perhaps there is another way. Here is the code for setting up my middleware and Apollo client:
const authLink = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => {
const token = localStorage.getItem('token');
const tokenExp = token ? decodeJWT(token).exp : null;
const currentTime = Date.now() / 1000;
if(token && tokenExp >= currentTime) {
// Check if token is expired. If so, get a new one and THEN
// move forward
headers = { ...headers,authorization: token ? `Bearer ${token}` : "", };
return { headers };
} else {
// TODO: This would be replaced with the token service that actually
// takes an expired token and sends back a valid one
return fetch('http://localhost:4000/topics', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `mutation LOGIN_USER(
$email: String
$password: String!
) {
login(email: $email, password: $password) {
id
token
}
}
`,
variables: {
email: "test#test.com",
password: "test"
}
}),
}).then(response => {
return response.json()
})
.then(({ data: { login: { token } }}) => {
// Put updated token in storage
localStorage.setItem('token', token);
headers = { ...headers,authorization: token ? `Bearer ${token}` : "", };
return { headers };
});
}
});
return forward(operation);
});
/**
* Setup the URLs for each service
*/
const httpTopicsServiceLink = createHttpLink({
uri: 'http://localhost:4000/topics',
});
/**
* Create the client instance for each GraphQL server URL
*/
export const TopicsClient = new ApolloClient({
link:authLink.concat(httpTopicsServiceLink),
cache: new InMemoryCache(),
});
You can return your own Promise that will resolve either with the headers or another Promise from your fetch request:
const authLink = new ApolloLink(async (operation, forward) => {
return await operation.setContext(({ headers = {} }) => {
const token = localStorage.getItem('token');
const tokenExp = token ? decodeJWT(token).exp : null;
const currentTime = Date.now() / 1000;
return new Promise((resolve, reject) => {
if(token && tokenExp >= currentTime) {
// Check if token is expired. If so, get a new one and THEN
// move forward
headers = { ...headers,authorization: token ? `Bearer ${token}` : "", };
resolve({ headers });
} else {
// TODO: This would be replaced with the token service that actually
// takes an expired token and sends back a valid one
resolve(fetch('http://localhost:4000/topics', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `mutation LOGIN_USER(
$email: String
$password: String!
) {
login(email: $email, password: $password) {
id
token
}
}
`,
variables: {
email: "test#test.com",
password: "test"
}
}),
}).then(response => {
return response.json()
})
.then(({ data: { login: { token } }}) => {
// Put updated token in storage
localStorage.setItem('token', token);
headers = { ...headers,authorization: token ? `Bearer ${token}` : "", };
return { headers };
}));
}
});
}).then(res => {
return forward(operation);
});
});
Can't test this so I may have missed something, but that should ensure the request is finished before forwarding.
To be able to add a promise for an ApolloLink you can add fromPromise and toPromise
import { ApolloLink, fromPromise, toPromise } from '#apollo/client';
const withToken = new ApolloLink((operation, forward) => {
return fromPromise(
fetch(...).then(({ token }) => {
operation.setContext(({ headers }) => ({
headers: {
...headers,
authorization: `Bearer ${token}`,
},
}));
return toPromise(forward(operation));
}),
);
});
ApolloLink expects an Observable which fromPromise will help convert the promise from your fetch to the correct type.

LocalStorage not working after storing token

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()
});
}

Resources