Middleware to verify/update JWT access and refresh tokens - reactjs

I have an app with JWT authentication written in React/ Django / Django-allauth.
I have an endpoint to verify/ refresh my access token and it works fine. My question is regards to where to put the refresh logic so it is automatically processed before each request? Is there middleware I can use or is there a way to override fetch?
Essentially, I want the app to verify the token, refresh it if necessary, and redirect unauthenticated user to login for every request dependent on JWT authorization. I also don't want to rewrite this logic over and over.
I'm thinking of overriding fetch
async function handle_token() {
const {valid, status} = await API.process_token()
return {
status,
valid,
}
}
// initialize the fetch object to minimize code repeat at every request
// https://stackoverflow.com/questions/44820568/set-default-header-for-every-fetch-request
function updateOptions(options) {
const update = { ...options }
update.headers = Object.assign({
'Content-Type': 'application/json',
'Accept': 'application/json'
}, update.headers ? update.headers : {})
if(update.jwt) {
const token = localStorage.getItem('access') ? localStorage.getItem('access') : ''
update.headers = Object.assign(update.headers, {'Authorization': `Bearer ${token}`})
/*******************************************************************************
* Perhaps put token logic here but unser how to to handle it
********************************************************************************/
const {valid, status} = handle_token()
}
return update;
}
function fetcher(url, options) {
return fetch(url, updateOptions(options));
}
export default fetcher;
Or maybe there is a middleware that is common to use? Thanks

Related

how can i pass a token to the header to avoid un authorization error?

I am working on a project, and prior to now, I have been able to Interact with all my protected APIs routes. But now each time I try to make any request that has Authorization I get a 401 unauthorized error even though am logged in and my token is available. I really don't know what is happening and why the token is not being passed into the headers. I have uninstalled the node and installed it again. I have deleted the project from my local PC and cloned it again from the master branch on the repository, it's still to no avail.
below are the codes.
Login
//continue with account login
setShowSpinner(true);
const { token } = await LoginUser({ email, password });
console.log(token);
localStorage.setItem('fagora-token', token);
if (window.history.state && window.history.state.idx > 0) {
navigate(-1);
} else {
navigate('/market-place', { replace: true });
}
enter code here
From the above i am able to console log the token.
[The token being console log ][1]
[1]: https://i.stack.imgur.com/ld3tZ.png
The Axios script
import axios from 'axios';
import { URL as baseURL } from "../../AppParams";
import { auth_token } from '../../AppParams';
import responseErrorInterceptor from './interceptor';
const api = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
// 'Accept': 'application/json'
},
});
api.interceptors.request.use(
config => {
if (auth_token) {
config.headers['Authorization'] = `Bearer ${auth_token}`
}
return config
},
error => Promise.reject(error)
);
responseErrorInterceptor(api);
export default api;
The AppParam where the token is stored
export let URL = process.env.REACT_APP_ENV === 'local' ? process.env.REACT_APP_LOCAL_URL : (process.env.REACT_APP_ENV === 'staging' ? process.env.REACT_APP_STAGING_URL : (process.env.REACT_APP_ENV === 'production' ? process.env.REACT_APP_PRODUCTION_URL : ''));
export let APP_NAME = process.env.REACT_APP_APP_NAME;
export let auth_token = localStorage.getItem('fagora-token');
All the routes are working perfectly fine on the postman. the token when passed to the headers works fine. But on my frontend, when I login and then try to make any request, it give unauthorized error.
I need help. And I will be available for further clarity where needed.
thanks.

How to modify axios instance after exported it in ReactJS?

I am using:
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
to set header after user make login in application, but when refresh the page this configuration is removed.
I would like to set this configuration for all requests from axios, when user make login.
I got do that setting this configuration manually, putting this line of code before to export axios instance.
Now, I need to set this configuration when user make login. How can I do that?
You're probably going to want to write a middleware module to get/set the token in localStorage and apply it to your Axios instance. In the past when I used Axios, I typically did it like this:
import axios from 'axios';
import { API_URL } from '../constants/api';
const API = axios.create({
baseURL: `${API_URL}`,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
API.interceptors.request.use(
config => {
const token = sessionStorage.getItem('jwt');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
} else {
delete API.defaults.headers.common.Authorization;
}
return config;
},
error => Promise.reject(error)
);
export default API;
You'll need to create functions to get/set the JWT in localStorage, but if you do that, this should work for you. This will fetch the JWT from localStorage before making each request, so it won't break even if the page is refreshed as long as the user has a valid JWT in localStorage.
I have the same issue as Hiatt described:
refresh the page will invalidate my previous default config
while I don't feel like reading storage before every request
so what I did is check before request and read if necessary (eg: default were reset due to page reload
// request interceptor function
function check_before_request(config) {
if (! config.headers.common['Authorization']) {
const token = Cookies.get('Authorization')
if (! token){
removeCookies()
location.href = `/login?redirect=${encodeURIComponent(location.pathname)}`
}
else {
setHeaderAuth(token, config)
}
return config
}
else return config
}
// also can be used in login page but without the second param
function setHeaderAuth(token, config) {
Cookies.set('Authorization', token)
axios.defaults.headers.common['Authorization'] = token;
if (config){
config.headers['Authorization'] = token
}
}

How to add new authorization header to Apollo client within a React component?

How can I add a token to an Apollo client authorization header within a react component?
I have a login function which passes a Google authorization code to the backend, receives an encrypted token and I want to add this to the authorization header so every request made after this contains it and can be validated on the backend to protect the API routes.
This token is returned to a React component, but I am not sure what command to use to do this? I assume client.writeData is meant for local storage but not in the context of headers.
Here's the code I have in my React component:
export default function LoginForm() {
function userLogin(code){
let token = googleAuthenticate(code.code);
if(token === Error()){
console.log("poop");
}else{
// ADD NEW AUTHORIZATION HEADER HERE.
}
}
return(<div>
<GoogleLogin
clientId="xyz"
buttonText="Login"
onSuccess={userLogin}
onFailure={userLogin}
cookiePolicy={'single_host_origin'}
hostedDomain={"blabla.com"}
responseType={"code"}
/>
</div>
);
};
One method is to use localStorage() like this:
function userLogin(code){
let token = googleAuthenticate(code.code);
if(token === Error()){
console.log("poop");
} else {
localStorage.setItem('token', token);
}
}
Then modifying the ApolloClient code accordingly:
const client = new ApolloClient({
request: (operation) => {
const token = localStorage.getItem('token');
operator.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
}
}
})
You'll have to write some code for when you logout to wipe the token, etc.,
This can be found on the ApolloGraphQL.com site too.

React Relay Modern redirecting to another page when receiving 401 error on network environment

I´m using JWT authentication inside my ReactJS RelayJS network environment. All the token retrieval and processing in server and client are fine. I´m using react router v4 for routing.
My problem is when I receive a Unauthorized message from server (status code 401). This happens if the user points to an application page after the token has expired, ie. What I need to do is to redirect to login page. This is the code I wish I could have:
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
const SERVER = 'http://localhost:3000/graphql';
const source = new RecordSource();
const store = new Store(source);
function fetchQuery(operation, variables, cacheConfig, uploadables) {
const token = localStorage.getItem('jwtToken');
return fetch(SERVER, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
Accept: 'application/json',
'Content-type': 'application/json'
},
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables
})
})
.then(response => {
// If not authorized, then move to default route
if (response.status === 401)
this.props.history.push('/login') <<=== THIS IS NOT POSSIBLE AS THERE IS NO this.history.push CONTEXT AT THIS POINT
else return response.json();
})
.catch(error => {
throw new Error(
'(environment): Error while fetching server data. Error: ' + error
);
});
}
const network = Network.create(fetchQuery);
const handlerProvider = null;
const environment = new Environment({
handlerProvider, // Can omit.
network,
store
});
export default environment;
Naturally calling this.props.history.push is not possible as the network environment is not a ReactJS component and therefore has no properties associated.
I´ve tried to throw an error at this point, like:
if (response.status === 401)
throw new Error('Unauthorized');
but I saw the error on the browser console, and this cannot be treated properly in the code.
All I wanna do is to redirect to login page in case of 401 error received, but I can´t find a proper way of doing it.
I am not using relay but a render prop. I experienced kind of the same issue. I was able to solve it using the window object.
if (response.statusText === "Unauthorized") {
window.location = `${window.location.protocol}//${window.location.host}/login`;
} else {
return response.json();
}
You can go with useEnvironment custom hook.
export const useEnvironment = () => {
const history = useHistory(); // <--- Any hook using context works here
const fetchQuery = (operation, variables) => {
return fetch(".../graphql", {...})
.then(response => {
//...
// history.push('/login');
//...
})
.catch(...);
};
return new Environment({
network: Network.create(fetchQuery),
store: new Store(new RecordSource())
});
};
// ... later in the code
const environment = useEnvironment();
Or you can create HOC or render-prop component if you are using class-components.
btw: this way you can also avoid usage of the localStorage which is slowing down performance.

How to implement login authentication using react-redux?

After a bit of research, JWT is commonly used for login authentication because of its compact nature and easiness to parse. I have settled on using JWT. However, my question is on how to embed this in my redux paradigm. Assuming we have a sign up form, when a user fills in his or her credentials and clicks a submit button, this will invoke an action to create an action to create a JWT. Now, this action goes to the back-end of my application and the back-end of my application calls the JWT API? So this action is an asynchronous/rpc call? Also, how does routing happen exactly? I have used react-router before, but using a boilerplate. I am building this web app from scratch and so I am a bit confused on where to deal with the routing and where do I pass this token exactly that I obtain from the server the first time? Is the token used every time a user does a request? How does the client know about this token every time it does the request so that it would keep a user authenticated?
When a user submits his credentials (email/password) your backend authenticates that for the first time and only this time does the backend use these credentials. On authentication your backend will create a JWT with some of the user information, usually just the user ID. There are plenty of JWT Libraries and even jwt-decode for javascript to do this. The backend will respond with this JWT where the front-end will save it (ie, localStorage.setItem('authToken', jwt)) for every subsequent request.
The user will send a request with the JWT in the request header under the Authorization key. Something like:
function buildHeaders() {
const token = localStorage.getItem('authToken')
return {
"Accept": "application/json",
"Content-Type": "application/json"
"Authorization": `${token}`
}
}
Your backend will now decode and authenticate the JWT. If it's a valid JWT the request continues, if not it's rejected.
Now with React-Router you can protect authenticated routes with the onEnter function. The function you provide does any necessary checks (check localStorage for JWT and if a current user). Typically I've done this:
const _ensureAuthenticated = (nextState, replace) => {
const { dispatch } = store
const { session } = store.getState()
const { currentUser } = session
const token = localStorage.getItem("phoenixAuthToken")
if (!currentUser && token) { // if no user but token exist, still verify
dispatch(Actions.currentUser())
} else if (!token) { // if no token at all redirect to sign-in
replace({
pathname: "/sign-in",
state: { nextPathname: nextState.location.pathname}
})
}
}
You can use this function in any route like so:
<Route path="/secret-path" onEnter={_ensureAuthenticated} />
Check out jwt.io for more information on JWT's and the react-router auth-flow example for more information on authentication with react-router.
I personally use Redux saga for async API calls, and I'll show You the flow I've been using for JWT authorization:
Dispatch LOG_IN action with username and password
In your saga You dispatch LOGGING_IN_PROGRESS action to show e.x. spinner
Make API call
Retrieved token save e.x. in localstorage
Dispatch LOG_IN_SUCCESS or LOG_IN_FAILED to inform application what response did You get
Now, I always used a separate function to handle all my requests, which looks like this:
import request from 'axios';
import {get} from './persist'; // function to get something from localstorage
export const GET = 'GET';
export const POST = 'POST';
export const PUT = 'PUT';
export const DELETE = 'DELETE';
const service = (requestType, url, data = {}, config = {}) => {
request.defaults.headers.common.Authorization = get('token') ? `Token ${get('token')}` : '';
switch (requestType) {
case GET: {
return request.get(url, data, config);
}
case POST: {
return request.post(url, data, config);
}
case PUT: {
return request.put(url, data, config);
}
case DELETE: {
return request.delete(url, data, config);
}
default: {
throw new TypeError('No valid request type provided');
}
}
};
export default service;
Thanks to this service, I can easily set request data for every API call from my app (can be setting locale also).
The most interesting part of it should be this line:
request.defaults.headers.common.Authorization = get('token') ? `Token ${get('token')}` : '';`
It sets JWT token on every request or leave the field blank.
If the Token is outdated or is invalid, Your backend API should return a response with 401 status code on any API call. Then, in the saga catch block, you can handle this error any way You want.
I recently had to implement registration and login with React & Redux as well.
Below are a few of the main snippets that implement the login functionality and setting of the http auth header.
This is my login async action creator function:
function login(username, password) {
return dispatch => {
dispatch(request({ username }));
userService.login(username, password)
.then(
user => {
dispatch(success(user));
history.push('/');
},
error => {
dispatch(failure(error));
dispatch(alertActions.error(error));
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}
This is the login function of the user service that handles the api call:
function login(username, password) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
};
return fetch('/users/authenticate', requestOptions)
.then(response => {
if (!response.ok) {
return Promise.reject(response.statusText);
}
return response.json();
})
.then(user => {
// login successful if there's a jwt token in the response
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
}
return user;
});
}
And this is a helper function used to set the Authorization header for http requests:
export function authHeader() {
// return authorization header with jwt token
let user = JSON.parse(localStorage.getItem('user'));
if (user && user.token) {
return { 'Authorization': 'Bearer ' + user.token };
} else {
return {};
}
}
For the full example and working demo you can go to this blog post

Resources