When to refresh a JWT token? - vue.js

Currently I am using JWT-Auth on my Laravel back-end to protect my API routes with a token. However, after a certain time the token gets invalid and I get the error 401 Unauthorized. So I guess I have to refresh the token somewhere. When would be the best time to do this? I read about doing it every time you make a request but I want to be sure that’s the right way to do so. I used this guide from their docs: https://jwt-auth.readthedocs.io/en/develop/quick-start/#create-the-authcontroller. In here they make a function to fresh a token. But how would I implement this every time I make a request? Do I just call this function in the controller with an Axios request or call it in another controller or something? Any tips are greatly appreciated.
I have a Vue.js front-end by the way.

With Tymon/JWTAuth you have two options:
You can add the jwt.refresh middleware to your api routes, which will refresh the token everytime a request is made. The downside of this solution is that this could be abused. The upside is that you do not really need to worry about the token in your application, especially if you do not have a frontend or do not develop the frontend yourself.
You parse the token client side. The first two parts of a jwt token are completely public and are base64-encoded. You don't really need to know if this token was signed by the server client-side, so you can safely ignore the last part. This solution is relatively easy if you have a wrapper around api calls that handles common logic for api calls (e.g. adding the authorization header to begin with).
const token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHBzOi8vZXhhbXBsZS5jb20iLCJpYXQiOjE1NTUzNDkxMjYsImV4cCI6MTU1NTM3NzkyNiwibmJmIjoxNTU1MzQ5MTI2LCJqdGkiOiJtZEdTNGE2ZDJnNHM5NzRnNSJ9.TygbG5smlhAapE8fy4rgXlLVYW-qOcWtLYnnbgJCIKg";
function shouldRefreshToken(token) {
const currentTime = 1555350309829; // Date.now()
const universalTimestamp = currentTime / 1000;
const gracePeriod = 60 * 60 * 8; // 8 hours
const tokenParts = token.split('.');
const payload = JSON.parse(atob(tokenParts[1]));
if (payload.iat > universalTimestamp) {
console.log("This monstrosity was issued in the future O_o");
}
if (payload.nbf > universalTimestamp) {
console.log("This token is not valid yet. Refreshing it does not yield anything useful. Maybe we still have some previous token?");
}
if (payload.exp < universalTimestamp) {
console.log("This token has expired. We should try to refresh it before doing anything else.");
} else if (payload.exp - gracePeriod < universalTimestamp) {
console.log("This token is about to expire. We can refresh it asynchronously.");
} else {
console.log("Nah, we are fine!");
}
}
shouldRefreshToken(token);
In the end you would want to send a request to a refresh endpoint that does something like this, which is then parsed by the frontend:
$myNewToken = JWTAuth::refresh(JWTAuth::getToken());
response()->header('Authorization', "Bearer {$myNewToken}");
To get it to work, you can do something like this:
import store from '../store';
import { shouldRefreshToken } from '../helpers/auth';
const someBaseUrl = 'https://example.com';
export function request(options = {}) {
// Hopefully you rewrite that function above to return a boolean ;-)
if (shouldRefreshToken(store.state.auth.token)) {
refreshToken();
}
const config = {
method: options.method,
url: `${someBaseUrl}/${options.resource}`,
credentials: 'include',
headers: {
...(options.headers || {}),
Authorization: `Bearer ${store.state.auth.token}`,
'Content-Type': 'application/json'
},
data: options.data
}
return axios(config).then(parseResponse)
}
function parseResponse(axiosResponse) {
// Probably want to get the token and do something with it
}
function refreshToken() {
axios({
method: 'POST',
url: `${someBaseUrl}/refresh`
}).then(parseResponse)
}

Related

Auth0 refresh token in React Native fails with 401

In my React Native app -- init app not Expo -- I'm trying to refresh the access_token but my POST call is failing with 401. I'm testing this functionality so I make the POST call some 30 seconds after I login so not sure if this plays a role or not.
In my initial login, I do get a refresh_token along with a valid access_token. I then tell my app to wait 30 seconds and make a POST call that looks like this:
const url = 'https://mydomain.auth0.com/oauth/token';
const postOptions = {
method: 'POST',
url: url,
headers: {
"content-type": 'application/x-www-form-urlencoded'
},
form: {
grant_type: 'refresh_token',
client_id: 'MY_CLIENT_ID',
refresh_token: 'REFRESH_TOKEN_RECEIVED_DURING_LOG_IN'
}
};
fetch(url, postOptions)
.then((response) => {
debugger;
// this is where I get response.status 401
})
Any idea what the issue is here?
Also want to mention that under my application settings, Refresh Token is checked under "Grant Types" but refresh token rotation or expiration are NOT enabled.
I figured this out and sharing it in case others need it in the future.
First, Auth0 documentation is misleading at best. They keep mentioning a regular POST call which doesn't work.
In my React Native app, I use their react-native-auth0 library. This library does offer a refreshToken() method which is what I ended up using.
Before I share the code, here are a couple of really important points:
Be sure to include offline_access in the scope of your initial authentication call for the user. Without including offline_access in your scope, you won't get a refresh_token. Once you receive it along with your access_token and id_token, store it as you'll use it many times. This brings me to the second point.
Unless you set it otherwise, your refresh_token doesn't expire. Therefore, store it some place secure and NOT just in AsyncStorage. As mentioned above, unless, you set it otherwise or it gets revoked, your refresh_token doesn't expire and you use it again and again.
With that said, here's the code. Please keep in mind that at start up, I initialize auth0 as a global variable so that I can access it in different parts of my app.
Here's what my initialization looks like in index.js:
import Auth0 from 'react-native-auth0';
global.auth0 = new Auth0({
domain: "MY_DOMAIN.auth0.com",
clientId: "MY_CLIENT_ID",
});
And here's how I use the refreshToken() method:
// First, retrieve the refresh_token you stored somewhere secure after your initial authentication call for the user
global.auth0.auth.refreshToken({ refreshToken: 'MY_REFRESH_TOKEN' })
.then(result => {
// If you're doing it right, the result will include a new access_token
})
you probably need to add the authorization header with your access_token:
const url = 'https://mydomain.auth0.com/oauth/token';
const postOptions = {
method: 'POST',
url: url,
headers: {
"content-type": 'application/x-www-form-urlencoded',
"Authorization" 'bearer '+access_token,
},
body: JSON.stringify({
grant_type: 'refresh_token',
client_id: 'MY_CLIENT_ID',
refresh_token: 'REFRESH_TOKEN_RECEIVED_DURING_LOG_IN'
});
};
fetch(url, postOptions)
.then((response) => {
debugger;
// this is where I get response.status 401
})

Checking the validity of JWT Tokens - beforeEnter

I've got a function that runs 'beforeEnter' in the Vue router to verify that the user has been authenticated, otherwise triggers a message.
It checks to see if a (jwt) token is saved in the localStorage - this works if the user signs out manually, as it removes the token from the localStorage. However when the token expires it still remains in the localStorage so the function thinks ((localStorage.token)) the user is logged in.
The server still blocks any requests made as the token is invalid - so is safe.
How do I check the token's validity on the server side, in the 'beforeEnter' middleware, before the page loads?
Do I need to make an endpoint that checks a tokens validity and returns the result? (I'm using fetch(), however I've seen people use axios interceptors...)
Worth nothing that I'm not using VUEX, and there seems to be more details on that?
function protectedPage(to, from, next) {
if (localStorage.token) {
next();
} else {
Vue.toasted.show("The session has ended. Please login.", {
theme: "toasted-primary",
position: "top-center",
duration: null,
action: {
text: "Login",
onClick: (e, toastObject) => {
next("/");
toastObject.goAway(0);
}
}
});
next("/");
}
}
Since exp is part of the payload, and JWT is just a base64 string, you can just decode it and check the exp time on your Vue app.
This is a function to decode JWT token and get the payload (taken from here)
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(Buffer.from(base64, "base64").toString("ascii").split("").map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
};
and check it on your beforeRouteEnter function:
beforeRouteEnter (to, from, next) {
if (localStorage.token) {
const jwtPayload = parseJwt(localStorage.token);
if (jwtPayload.exp < Date.now()/1000) {
// token expired
deleteTokenFromLocalStorage();
next("/");
}
next();
} else {
next("/");
}
},
You don't really need to check it on your backend server, since there's no security concern by decoding the JWT token payload and checking it in on the client side. Plus it saves you one HTTP request every time a user access a route.
You need a backend middleware which bound to each API call and validates user session if still exists and has same tokens.
If the session has been expired or token has been changed and doesn't match with the current user session, you can redirect user to the login page from backend and force him to create a fresh session.
I think you don't need to fetch the authentication for each route entrance, just block the backend api calls and return a message or redirect to the login page. User can still browse the pages with the expired session info but won't be able to perform any fetch or form actions.

fetching inside express server - fetch is not defined

I am trying to get a long lived access token from facebook... according to the documentation, I need to fetch from the server side for security. If I fetch from the client side it works fine - but I should follow the guidelines.. Here is what I am doing - can you tell me why I get the server side error "Reference error: fetch is not defined"?
First I created a route from the front end to request the long lived token. To get the long lived token I need to use the short lived token so I send the short lived token in the req params. The variable userAccessTokenShort is valid and defined.
const newLongLivedUserAccessToken = await fetch(`/api/autoquotegenerators/longLivedUserToken/${userAccessTokenShort}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json; charset=UTF-8",
},
})
const newLongUserTokenRes = await newLongLivedUserAccessToken.json()
console.log(newLongUserTokenRes)
Then, on my express server, I made a route that handles the fetch request to the Facebook Graph API. This is where I get the reference error.
//Get Long Lived User Access Token on Backend /express route
router.get('/longLivedUserToken/:shortLived', (req, res) => {
try {
const getLongToken = fetch(`https://graph.facebook.com/v7.0/oauth/access_token?grant_type=fb_exchange_token&client_id=#############&client_secret=################&fb_exchange_token=${req.params.shortLived}`)
.then(token => res.send(token))
} catch (error) {
console.log(error)
}
})
If all goes well, the new token should be sent back to the client.
Thanks for your help!
There is no fetch() function built into node.js. If you want something that looks pretty much like fetch() in the browser, you can look at the node-fetch module in NPM.
Or, there are lots of modules you can choose from in node.js. My favorite is got().
FYI, the built-in interface for fetching http requests is http.request() or http.get(), but they are lower level and require more lines of code to do something simple than any of the above named modules.

Angular 2 AuthHttp with jwt not connecting

I'm trying to use jwt's authHttp to set an API connection to a particular Back End. I'm trying to make it first without any token so I can test it but it seams like it's not even getting connected. I'm using it as following:
this.authHttp.get('localhost:3001/api/basic')
.subscribe(
data => console.log("data"),
err => console.log(err),
() => console.log('Request Complete')
);
The error I'm getting in the console is AuthHttpError {}
I've set my ngModules as it say in the guide:
providers: [
{
provide: AuthHttp,
useFactory: authHttpServiceFactory,
deps: [Http, RequestOptions]
}
And
function authHttpServiceFactory(http: Http, options: RequestOptions) {
return new AuthHttp(new AuthConfig({noTokenScheme : true}), http);
}
The thing that drive's me crazy is that using http it works fine like this:
this.http.get('http://localhost:3001/api/basic').subscribe(
data=> console.log(data),
error=> console.log("Getting Error")
);
You are probably thinking "Why he is not using http then instead of authHttp?". Well, that's because setting a heather "Authorization" and its token seams impossible with http.
Any help or guidance would be extremely helpful.
If you don't need JsonWebTokens but simply want to add custom headers, you can do it this way without having to import the angular2-jwt library :
In your service :
private customHeaders: Headers = this.setCredentialsHeader();
setCredentialsHeader() {
let headers = new Headers();
let credentials = window.localStorage.getItem('credentials2');
headers.append('Authorization', 'Basic ' + credentials);
return headers;
}
someMethod() {
let url = 'your.URL.to.API';
return this.http
.get(url, { headers: this.customHeaders })
.map(result => {
console.log(result);
});
}
This way you can add your Authorization header with the type of data you want.
If it's a Authorization Bearer type header you are looking for and use it with angular2-jwt, you can use the default configuration first before trying to provide your own AuthHttp instance through the factory. It will be much simpler to debug and figure where the problem is.
From the documentation : https://github.com/auth0/angular2-jwt#configuration-options
AUTH_PROVIDERS gives a default configuration setup:
In your module with your service, just import the AUTH_PROVIDERS like this :
import { AUTH_PROVIDERS } from 'angular2-jwt';
...
#NgModule({
...
providers: [
AUTH_PROVIDERS,
...
]
})
and simply use the AuthHttp instance in your service like you did.
You should see in the Navigator Network tab your headers being added to your request.
EDIT :
As stated in the documentation, it is appending the token value in the headers from the Token Getter Function defined in the AUTH_PROVIDERS by default.
You therefore need to add your JWT in your LocalStorage with the default name id_token.
To give you my working example, I'm setting a JWT upon the authentication process, where I get a JWT as a response from my Http Call :
auth.service.ts
this.identityService.setToken(token.accessToken);
identity.service.ts
setToken(token?) {
if (token) {
window.localStorage.setItem('id_token', token);
} else {
window.localStorage.removeItem('id_token');
}
}
You should be able to see your JWT in your network tab if done correctly.
Afterwards, the AuthHttp instance should add the headers to your requests as intended...
It might not work correctly if your Token is not a JWT. To check if it's a good one, you can use a website such as https://jwt.io/ where it will be decoded.
If it's still not working, this means the problem is coming from elsewhere. A service not provided correctly, etc.

Store api key in react webapp

My react web application uses axios to make an API post request.
The API needs a parameter called token for each post request.
Is there a way to always add the token parameter for each post request I do, so I don't have to add it manually every time, and where to save this token in a secure location?
I feel what I do now is a bit redundant. Example:
axios.post('apiUrl.com', {
token: 'abcdefg12345678',
userId: 1
}).then(() => {//do something});
Use axios interceptors. If you add a request interceptor, you can make a change (add token) to each request:
axios.interceptors.request.use(function(config) {
config.data = config.data || {};
config.data.token = 'abcdefg12345678';
return config;
});