I have developed a standard JWT system that logs in and issues an access token and a refresh token. The access token expires after a short amount of time and there is a route to refresh the token.
I use axios to make requests but I am not sure how to deal with expired tokens. For example, if I make a request to /secret_route with my access token and it's expired, do I need to wait for a 403 and then make a request to /refresh_token and then make the original request again? Seems messy from a programming point of view and quite wasteful on the network. Is there an efficient/elegant way to do this?
I ended up with a solution that I feel is more robust than checking the timestamp. Thanks #Bergi but I am concerned about the system clock. I use axios interceptors to refresh the token on a 401
// Request interceptor for API calls
axios.interceptors.request.use(
async config => {
config.headers = {
'Authorization': `Bearer ${localStorage.getItem("accessToken")}`,
'Accept': 'application/json',
}
return config;
},
error => {
Promise.reject(error)
});
// Allow automatic updating of access token
axios.interceptors.response.use(response => response, async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const res = await axios.post('/users/token', { token: localStorage.getItem('refreshToken') });
setToken(res.data.accessToken);
return axios.request(originalRequest);
}
return Promise.reject(error);
});
Adapted from https://thedutchlab.com/blog/using-axios-interceptors-for-refreshing-your-api-token
Related
I have an angular application and for some reason, the localstorage is showing the token from current User as null and therefore, the api return a 401 unauthorized. The issue happens after login and saving the token to localStorage and routes to the proper component to handle the next request. The request at this next page after login is returning 404 because the jwt did not find the token in the localstorage. Once I refresh the page, the api's start working again as if it found the token.
I have tried multiple approaches such as trying to inject localstorage, try async localstorage get, etc but nothing seems to work. Any help would be appreciated.
my JWT Interceptor is:
export class JwtInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add authorization header with jwt token if available
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
return next.handle(request);
}
}
The authentication Code is t which I get and save to user is(the use of this function in the component is under this):
login(merchantId: string, client_id: string, code: string) {
return this.http.post<any>(`${environment.apiUrl}/cloverApi/authenticate`, {merchantId: merchantId, client_id: client_id, code: code})
.pipe(map(token => {
// login successful if there's a jwt token in the response
this.role = token.role;
if (token && token.access_token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
this.setCookie('clover_access_token_cookie', 'Generic User', 364); //expires in 364 days as token only last 365
console.log('in clover auth.service login completed. Cookie Stored under username: ' + this.getLoggedInSubject()); //*MES*
return token;
}
}));
}
So I get the clover api token and set it to the current user and send an update request as shown here:
this.cloverTokenService.login(this.url_merchant_id, this.url_client_id, this.url_code)
.subscribe(
data => {
this.currentUser.cloverToken = data.access_token;
this.currentUser.merchantId = this.url_merchant_id;
this.userService.update(this.currentUser).subscribe((updatedUser:User)=> {
localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
localStorage.setItem('', '');
this.checkAuthAndRoute();
},
error => {
console.error(error);
})
},
error => {
console.error(error);
});
While setting your object in localStorage your token is under currentUser.cloverToken then when you retrieve it your trying to read currentUser.token
Either do this this.currentUser.token= data.access_token; or this Authorization: Bearer ${currentUser.cloverToken}
I believe you can make use of BehaviorSubject to pass token around as getting data from localstorage could have caused the issue somehow. You can next to the behavior subject when login succeeds and that can be used by the interceptor.
Note that you're using this.currentUser instead of the updatedUser inside the subscribe method.
And you are not setting this.currentUser.token anywhere in the snippets you provided. So, it's going to be undefined when you use it in your interceptor unless you are setting it somewhere else.
To confirm this, check your local storage from the browser. For example, in Chrome, it would be under the Application tab.
If currentUser appears there but doesn't have the token property then it's what I already mentioned.
If currentUser appears in the local storage with the token property, then you're probably not setting your interceptor correctly. In that case, you may find this article about interceptors helpful.
For example, I don't know if you omitted this on purpose but I don't see your interceptor annotated with the #Injectable() decorator.
I was just wondering if someone could help me figure this out. My code to get the access token is:
const inputBody = 'client_id=00000000-0000-0000-0000-000000000001&secret=mySecretPassword';
const headers = {
'Content-Type':'application/x-www-form-urlencoded',
};
fetch('https://api.replicastudios.com/auth',
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
I then need to include the access token received from the first call in order to insert it below.
const headers = {
'Authorization':'Bearer {token}'
};
fetch('https://api.replicastudios.com/voice',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
What is the best way to save the access token and insert it into the second request?
Please, and one more time, please don't store users security tokens in session storage, local storage, cookies (by yourself, let browser do it securely for you), etc.
Browsers have pretty awesome way of handling those security sensitive stuff, and those ways are preventing session hijacking through XSS and bunch of other stuff.
Proper way of doing things:
On the frontend you call your login (or in your case) auth route, and from the backend instead of sending your security token in body you need to set it in the secure cookie. One of the common ways to do it with Node+Express is like this:
res.status(200)
.cookie('auth_cookie', token, { httpOnly: true, maxAge: 86400 * 1000, sameSite: 'none', secure: true})
.json({ success: true, message });
This was one example of it, you should learn what all those arguments do here: https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector/Cookies
And on the last part, you dont need to manually include those cookies with each other request after that, if you are using Axios you can just put { withCredentials: true } and it's gonna automatically include those secured tokens in the cookie part of your request. There is a good thread already on that here: Make Axios send cookies in its requests automatically
I understand that this can seem as much work but it will make web safe(er) and it promotes good practices.
If your code is in a browser which it seems like it is, you could save the access token to session storage for use in future calls. When the tab is closed, session storage will be cleared.
const inputBody = 'client_id=00000000-0000-0000-0000-000000000001&secret=mySecretPassword';
const headers = {
'Content-Type':'application/x-www-form-urlencoded',
};
fetch('https://api.replicastudios.com/auth',
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
// NEW CODE
// body.access_token is my guess, you might have to change this
// based on the response
window.sessionStorage.setItem('access_token', body.access_token)
});
Then you can get the access token using:
window.sessionStorage.getItem('access_token')
I need some help understanding how to pass in a firebase bearer/jwt token with my api requests to the backend.
I am using axios and using an interceptor to set up the bearer token similar to this
axios.interceptors.request.use(
config => {
const userData = store.getters['firebaseAuth/user']
if (userData && userData.accessToken) {
config.headers['Authorization'] = 'Bearer ' + userData.accessToken
}
In all the examples for firebase auth, I have seen that the way to get the jwt token is like so:
firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then(idToken => {
Would it be good practice to run this in my interceptor to obtain the access token?..
Feels like an unnecessary extra api call to firebase servers for every request made to my backend server.
Also I have trouble understanding why I should even use this firebase method (getIdToken) when the token is available as a property from the user object. It has a strange name "za" which i think is deliberate from firebase, and nowhere mentions to use this to get the id/bearer/access token.
firebase.auth().onAuthStateChanged(user => {
if (user) {
//user.za id the id/bearer/access token
My initial thought was to just store user.za in localstorage then fetch it from local storage in the interceptor.
I'm not sure if it is the best way, but I ended up using the Firebase SDK methods in my interceptor directly. My understanding of the getIdToken API is that the promise returns a cached JWT, unless it is expired, in which case it will return a fresh one. I made the interceptor function async, and use await to synchronously request the token.
axios.interceptors.request.use(
async function (request) {
const user = currentUser();
if (user) {
try {
const token = await user.getIdToken(false); // Force refresh is false
request.headers["Authorization"] = "Bearer " + token;
} catch (error) {
console.log("Error obtaining auth token in interceptor, ", error);
}
}
return request;
},...
I have a http service that handles http requests. It has a handler that treats errors by popping up a modal on the screen.
I also have an interceptor that catches requests to insert an authorization token in case they need one. The token is taken from requesting another service.
The problem is, if the authorization request is needed and it fails, the modal error is shown twice even if the second request is never sent.
If think there's something wrong in the way I'm dealing with the Observable stream. Could someone help please?
/// http service
request(url, body, headers) {
return this.httpClient.post(url, body, headers).pipe(
catchError(() => this.handleError())
);
}
handleError() {
this.showModal();
return throwError('Theres been an error.');
}
/// interceptor
intercept(request, next) {
const authorization = request.headers.get('Authorization');
if (authorization) {
return this.httpService.request(url, body, headers).pipe(
exhaustMap(token => {
const newRequest = request.clone({
headers: request.headers.set(
'Authorization',
token
)
});
return next.handle(newRequest);
})
);
}
return next.handle(request);
}
Have you tried to console.log out the errors you're getting. It is possible that you're getting two differents errors.
Are you sure about this part of the code as well:
const newRequest = request.clone({
headers: request.headers.set(
'Authorization',
token
)
});
In some libraries setting headers after a request is sent is not permitted.
I have an ionic 2 mobile application that uses Json Web Tokens(JWT) to authenticate to various routes on a node.js API. These JWTs have a short expire time, and need to be refreshed using a refresh token. The refresh token is just a random string that is stored both in the database and on the mobile device. Please note: I am NOT using OAuth.
How can I refactor my API calls so that they all go through one method which will send a refresh token if the initial API call gets a 401 Unauthorized response due to an expired JWT? Otherwise, I will need to write the logic for handling that response in every single API call which I would like to avoid.
Here is an example of one method I have implemented in typescript that calls the API. It is not currently handling a 401 Unauthorized response nor is it sending the refresh token yet:
public setBeerPref(beerPrefs) {
return new Promise((resolve, reject) => {
this.storage.get('email').then((email) => {
this.storage.get('token').then((token) => {
beerPrefs["email"] = email;
let headers = new Headers();
headers.append('Authorization', token);
headers.append('Content-Type', 'application/json');
let options = new RequestOptions({ headers: headers });
this.http.post('apiURL', beerPrefs, options)
.subscribe(res => {
let data = res.json();
resolve(data);
resolve(res.json());
}, (err) => {
reject(err);
});
});
});
});
}