Renew access token with refresh token from personal api - javascript

I have a personal REST API which i am calling from an angular 2 client. My current authentication process is as follows:
User login with email/password
Server validates and sends user_data, access_token(JWT) and refresh_token(opaque token which is stored in the database) back to client.
Client sends access_token on every request.
On my API, i have and endpoint getAccessToken(String email, String refresh_token) which validates refresh_token and issues a new access_token.
My question is: what method should i use to request for a new access_token before or after it expires using my refresh_token from my angular2 app.
I'm currently thinking of checking for access_token expiration before each http request to the API as follows:
if (!tokenNotExpired("accessToken")) {
this.classService.getAccessToken().subscribe(
data => {
// store new access_token in localStorage, then make request to get resource.
this.classService.createClass().subscribe(
data => {
//success
}, error => {
//error
})
}, error => {
// Invalid refresh token, redirect to login page.
});
} else {
this.classService.createClass().subscribe(
data => {
//success
}, error => {
//error
});
}
Is there a better method to do this? I'm using angular2-jwt for token verification.

This seems quite alright. But if you have many different API calls (e.g. not only createClass() but also updateClass() and destroyClass()) you might end up with having to do the same token expiration check in many different places of your source code and that's never a good thing.
What about creating one service which will take care of token expiration/renewal for all your calls?
This service will work as a factory for observables. You tell it which endpoint should be contacted and what data to send to the endpoint. The service will return an observable based on your API request. And if the token is expired, it will wrap your observable in a token refresh call.
This is just a pseudo code to get the general idea:
class ApiService
{
constructor (private http: Http){}
createRequest (endpoint, payload):Observable<any>
{
let request =
this.http.post(endpoint, payload, {headers:auth}).map(/* ... */);
if (tokenExpired())
{
return this.refreshToken().flatMap(
(token) => {return request});
}
else
return request;
}
refreshToken():Observable<string>
{
/* ... */
}
}
With this service you don't have to check for token expiration anywhere else. Just make sure you use the service to construct your API calls, i.e. inside your createClass() method.
You can even enhance createRequest method by a parameter which will allow to switch off the authorization (e.g. for endpoints which don't require authorization). Another parameter could be used to create calls with different HTTP methods (PUT, GET).

I tried to mimic good old sliding expiration:
In auth.guard.ts:
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { tokenNotExpired,
JwtHelper } from 'angular2-jwt';
#Injectable()
export class AuthGuard implements CanActivate {
private jwtHelper = new JwtHelper();
constructor(private router: Router) {}
tokenValid() {
this.handleSlidingExpiration();
return tokenNotExpired();
}
canActivate() {
if (tokenNotExpired()) {
this.handleSlidingExpiration();
return true;
}
this.router.navigate(['/login']);
return false;
}
private handleSlidingExpiration() {
let token = localStorage.getItem('id_token');
if (!token) { return; }
let expirationDate = this.jwtHelper.getTokenExpirationDate(token);
let dToken = this.jwtHelper.decodeToken(token);
let refreshLimit = new Date((dToken.iat + (dToken.exp - dToken.iat) / 2) * 1000);
if(new Date() > refreshLimit) {
// Here you can make a new side request for the new token and update it in local storage
}
}
}

Related

Singleton service in Angular with async HTTP call?

I am developing a web application using Angular (2+) and I need to implement a service like this:
I have an endpoint (URL) and I use this URL to do a POST HTTP call. The response contains a token and a number of seconds indicating how long this token is valid.
My service has to do this thing: check if the token has expired (i.e. if the deadline has passed). If yes, it makes the HTTP call and gets a new token, if not, it returns the previously stored token to the client.
This is my code example:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class MyPOSTService {
private deadline;
private token; // Stringa in cui viene salvato il tokenSTS temporaneo
constructor(private http: HttpClient) { }
async getToken() {
if(!this.deadline || Math.ceil(new Date().getTime()) > this.deadline) {
await this.http.post(MY_URL, MY_BODY).toPromise().then((data: any) => {
if(data) {
this.token = data.token;
this.deadline = data.deadline + Math.ceil(new Date().getTime()/60000) - 1;
}
})
}
return this.token;
}
}
where MY_URL is the endpoint and MY_BODY is the request body. data.token is the token and data.deadline is the number of seconds indicating how long this token is valid.
Unfortunately this solution doesn't work. If a client calls the getToken() method multiple times after some time, due to the asynchronous call, this.deadline will always be undefined!
How do I make the call in synchronous way? So that the getToken() method becomes "atomically callable"?
Surely I am wrong approach but I hope I have been clear. How do I implement what I would like to achieve? What am I doing wrong?

Problem with JWT Refresh Token Flow with axios/axios-auth-refresh

(I've read a number of similar questions here, and most/all have said to use a different axios instance for the refresh token requests (versus the API requests). However, I'm not clear on how that would work, since I am using axios-auth-refresh for auto-refreshing the access tokens.)
I'm working on an app with a JWT-based authentication flow for back-end API requests. The general flow is working fine; upon login the user gets a long-term refresh token and short-term access token. Using the axios-auth-refresh plug-in for axios, I am able to auto-refresh the access token when it has expired.
My problem is, when the refresh token expires, I am not able to catch the error and redirect the user to re-authenticate. Nothing I've tried catches the error. The (current) code for the auto-refresh hook is:
const refreshAuth = (failed) =>
axios({ method: "post", url: "token", skipAuthRefresh: true })
.then(({ status, data: { success, accessToken } }) => {
console.warn(`status=${status}`);
if (!success) Promise.reject(failed);
processToken(accessToken);
// eslint-disable-next-line no-param-reassign
failed.response.config.headers.Authorization = `Bearer ${accessToken}`;
return Promise.resolve();
})
.catch((error) => console.error("%o", error));
createAuthRefreshInterceptor(axios, refreshAuth);
In cases of the refresh token being stale or missing, I see neither the status=xxx console line nor the dump of an error object in the catch() block.
The actual file this is in is on GitHub here, though it is slightly different than the working version above. Mainly, in the GH version the hook calls axios.post("token").then(...) where above I'm making a more explicit call to add the skipAuthRefresh parameter. Adding that got me more detailed error traces in the console, but I am still not catching the 401 response via the catch().
I've tried everything I can think of... anything jump out as something I'm missing?
Randy
(Edited to ensure the GitHub link points to the version of the file that has the issue.)
Since posting this, I have managed to work through the problem and come up with a working solution.
The key to the solution does in fact lie in using a different axios instance for the calls to renew the refresh token. I created a second module to encapsulate a second axios instance that would not get the interceptor created by the axios-auth-refresh module. After working around some inadvertent circular-dependency issues that this initially caused, I reached a point where I could see the exception being thrown by axios when the refresh token itself is stale or missing.
(Interestingly, this led to another problem: once I recognized that the refresh token was no longer valid, I needed to log the user out and have them return to the login screen. Because the application this is in is a React application, the authentication was being handled with custom hooks, which can only be called within a component. However, I had abstracted all the API calls into a non-React module so that I could encapsulate things like the addition of the Authorization header, the base URL, etc. At that level I could not run the auth hook to get access to the logout logic. I solved this by putting a default onError handler on the query object (a react-query object) that I use for all the API calls.)
I built upon the Request class from this SO answer to refresh the token and handle the refresh failures.
Now my Request looks like this:
import axios from "axios";
import {getLocalStorageToken, logOut, refreshToken} from "./authentication";
class Request {
ADD_AUTH_CONFIG_HEADER = 'addAuth'
constructor() {
this.baseURL = process.env.REACT_APP_USER_ROUTE;
this.isRefreshing = false;
this.failedRequests = [];
this.axios = axios.create({
baseURL: process.env.REACT_APP_USER_ROUTE,
headers: {
clientSecret: this.clientSecret,
},
});
this.beforeRequest = this.beforeRequest.bind(this);
this.onRequestFailure = this.onRequestFailure.bind(this);
this.processQueue = this.processQueue.bind(this);
this.axios.interceptors.request.use(this.beforeRequest);//<- Intercepting request to add token
this.axios.interceptors.response.use(this.onRequestSuccess,
this.onRequestFailure);// <- Intercepting 401 failures
}
beforeRequest(request) {
if (request.headers[this.ADD_AUTH_CONFIG_HEADER] === true) {
delete request.headers[this.ADD_AUTH_CONFIG_HEADER];
const token = getLocalStorageToken();//<- replace getLocalStorageToken with your own way to retrieve your current token
request.headers.Authorization = `Bearer ${token}`;
}
return request;
}
onRequestSuccess(response) {
return response.data;
}
async onRequestFailure(err) {
console.error('Request failed', err)
const {response} = err;
const originalRequest = err.config;
if (response.status === 401 && err && originalRequest && !originalRequest.__isRetryRequest) {
if (this.isRefreshing) {
try {
const token = await new Promise((resolve, reject) => {//<- Queuing new request while token is refreshing and waiting until they get resolved
this.failedRequests.push({resolve, reject});
});
originalRequest.headers.Authorization = `Bearer ${token}`;
return this.axios(originalRequest);
} catch (e) {
return e;
}
}
this.isRefreshing = true;
originalRequest.__isRetryRequest = true;
console.log('Retrying request')
console.log('Previous token', getLocalStorageToken())
try {
const newToken = await refreshToken()//<- replace refreshToken with your own method to get a new token (async)
console.log('New token', newToken)
originalRequest.headers.Authorization = `Bearer ${newToken}`;
this.isRefreshing = false;
this.processQueue(null, newToken);
return this.axios(originalRequest)
} catch (err) {
console.error('Error refreshing the token, logging out', err);
await logOut();//<- your logout function (clean token)
this.processQueue(err, null);
throw response;//<- return the response to check on component layer whether response.status === 401 and push history to log in screen
}
}
throw response;
}
processQueue(error, token = null) {
this.failedRequests.forEach((prom) => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
this.failedRequests = [];
}
}
const request = new Request();
export default request;
My problem is, when the refresh token expires, I am not able to catch
the error and redirect the user to re-authenticate. Nothing I've tried
catches the error. The (current) code for the auto-refresh hook is:
What is the return code from your api if the access token expired ?
if it is different than 401 (default) you need to configure, see exanoke 403:
createAuthRefreshInterceptor(axios, refreshAuthLogic, {
statusCodes: [ 401, 403 ] // default: [ 401 ]
});

How to properly deal with jwt token for logout & handling multiple urls using ionic /Angular

I am trying to build a app in ionic framework using angular on below topics i am unable to apply or say i dont know exact procedure for implementing that below are my key issues:
How can i can check the token expiry date so that if token is expired i can logout the user .
How can i maintain the token through out the application using storage right now i manually calling the constructor in the service ?
How can i toggle the url based on the token i.e if token is there i have to send x/url if token is not there i have to send y/url
what i have done
Login.page.ts
this.Auth.authenticate(this.loginForm.value).then((res: any) => {
this.loading = false;
if (res.idToken.payload['cognito:groups']) {
if (res.idToken.payload['cognito:groups'].length > 0) {
admin = 'true';
} else {
admin = 'false';
}
} else {
admin = 'false';
}
this.storage.set('authToken',res.accessToken.jwtToken);
this.storage.set('isLogged',true);
this.navCtrl.navigateBack('/tabs/tab1');
}).catch((err => {
this.loading = false;
this.toastServ.showToast(err.message, 'error');
}));
"http.interceptor.ts"
export class HttpConfigInterceptor implements HttpInterceptor {
token:any;
constructor(public storage: Storage ) {
console.log('http interceptor');
this.storage.get('authToken').then(data => {
this.token = data;
});
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('intercept');
if (this.token) {
request = request.clone({ headers: request.headers.set('Authorization', this.token) });
}
if (!request.headers.has('Content-Type')) {
request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });
}
request = request.clone({ headers: request.headers.set('Accept', 'application/json') });
return next.handle(request).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
}
return event;
}));
}
}
user.service.ts
right now i am checking the token like this
checkUserToken(){
this.storage.get('authToken').then(data => {
this.url = this.url + 'someotherurl/';
this.token = data;
});
}
getLatLong(): Observable<any>{
this.checkUserToken();
const endPoint = this.url + 'availableData';
return this.http.get(endPoint).pipe(map(response => {
return response;
}), catchError(error => {
return 'Unable establish connection!';
}));
}
1) How can i can check the token expiry date so that if token is expired i can logout the user
When you login and as for the token, it will return the expiry data too inside the object. What you can do is to store the expiry date in the Local Storage too so you can check it and handle it. For example, you can check for the expiry date when you start the app, you will have three possibilities:
There is neither a token nor an expiry date (first time), so you will go to the login page.
The token has been expired as the expiry date is less than the current date. In this case you will need to go to the login page too to get a new one.
The token is valid and the expiry date is greater than the current date. You have a session so there is no need to go to the login page. You can set a timeout with the difference between the two dates and when that timeout comes, you will display a popup to the user and go to login page. For example, if the current time is 09:00 and the expiry is 09:15, you will create a timeout with 15 mins that will show a popup to user once passed.
2) How can i maintain the token through out the application using storage right now i manually calling the constructor in the service ?
You can keep it in a central singleton service that you can always get / update the token using it. You can have an init method that will be called in the App Component to initialize the above logic.
3) How can i toggle the url based on the token i.e if token is there i have to send x/url if token is not there i have to send y/url
From the above logic, you can easily differentiate between the two states. So you can choose which API you want to call easily.

When to use guards and when to use middlewares in NestJs

I want to create a NestJs app and want to have a middleware validating the token in the request object and a authentication guard validating the user in the token payload.
By splitting this I was hoping to have a clean separation. First my middleware
#Injectable()
export class TokenMiddleware implements NestMiddleware {
use(req: any, res: Response, next: NextFunction) {
try {
const headers: IncomingHttpHeaders = req.headers;
const authorization: string = headers.authorization;
const bearerToken: string[] = authorization.split(' ');
const token: string = bearerToken[1];
// !! Check if token was invalidated !!
req.token = token;
req.tokenPayload = verifyToken(token);
next();
} catch (error) {
throw new UnauthorizedException();
}
}
}
It only validates the token and extends the request object with the encoded token and its payload. My auth guard
#Injectable()
export class AuthenticationGuard implements CanActivate {
constructor(private readonly usersService: UsersService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request: any = context.switchToHttp().getRequest();
try {
const user: any = request.tokenPayload;
if (!user) {
throw new Error();
}
const findByIdDTO: FindByIdDTO = { id: user.id };
const existingUser: UserRO = await this.usersService.findById(findByIdDTO);
if (!existingUser) {
throw new Error();
}
// attach the user to the request object?
return true;
} catch (error) {
throw new UnauthorizedException();
}
}
}
This guard checks if the provided user in the tokenpayload is a valid one. If everything is fine, where should I attach the user to the request object? As far as I know the guard only checks if something is correct. But I don't want to keep all this logic in the token middleware. Where can I attach the database user to the request after finishing the validation in the auth guard?
If you want to do something similar to Passport you could always attach the user to req.user, which is seen as a pretty standard ting in the Node.JS world.
Side question for you: any reason to not have two guards that function right after another? Have one guard for checking that the token is there and is indeed a valid token and one for validating the user on the token is indeed a valid on. That way you don't use a middleware (which is kind of included mostly for the sake of compatibility) and still have the separated logic.
Where can I attach the database user to the request after finishing the validation in the auth guard?
I believe that Guard, as you noticed, should validate if given user has the right to use given method.
Depending on your needs, you can go into different paths:
1) use passport and a strategy to do what you need (https://stackoverflow.com/a/57929429/4319037 I wrote a few words and lines about this already). Furthermore, it will already cover most of the code you have to extract the token.
#Injectable()
export class HttpStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super()
}
async validate(token: string) {
const user = await this.authService.findUserByToken(token);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
2) use Interceptor on controller/method level to attach the user to given request (and throw if token is missing); your Guard will receive the user already, thus you can validate if the user has correct role/rights to execute the method.
Please let me know if I misunderstood what you want to achieve or need more details on particular way, thanks!

Next.js getInitialProps cookies

I have encountered an issue regarding fetching data from the getInitialProps function in Next.js
The scenario is this: when a user first visits a page, I make an HTTP request to a distant API which returns me data that I need for the application. I make the request inside the getInitialProps method because I want the content to be fully rendered when I ship the content to the user.
The problem is, when I make this request, the API returns me a session cookie which I need to store inside the browser, not the server that is rendering the content. This cookie will have to be present inside future client-side requests to the API. Otherwise, the API returns me 403.
My question is: If I'm performing this request from the server, and because of that the response also comes back to the server, How can I set the cookie for the browser so that I could make client-side requests to the API?
I tried manipulating the domain option of the cookie but I cannot set another domain. The browser just ignores it.
Here is how my getInitialProps looks like:
static async getInitialProps(appContext) {
const { Component, ctx, router } = appContext;
const { store } = ctx;
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(appContext);
}
const { hotelId, reservationId } = router.query;
if (!hotelId || !reservationId) return { pageProps };
// Fetching reservation and deal data
try {
const { data, errors, session } = await fetchData(hotelId, reservationId);
if (data) {
store.dispatch(storeData(data));
}
// This works, but the domain will be the frontend server, not the API that I connecting to the fetch the data
if (session) {
ctx.res.setHeader('Set-Cookie', session);
}
// This doesn't work
if (session) {
const manipulatedCookie = session + '; Domain: http://exampe-api.io'
ctx.res.setHeader('Set-Cookie', manipulatedCookie);
}
if (errors && errors.length) {
store.dispatch(fetchError(errors));
return { errors };
} else {
store.dispatch(clearErrors());
return {
...pageProps,
...data
};
}
} catch (err) {
store.dispatch(fetchError(err));
return { errors: [err] };
}
return { pageProps };
}
The fetchData function is just a function which sends a request to the API. From the response object, I'm extracting the cookie and then assign it to the session variable.
getInitialProps is executed on the client and server. So when you write your fetching function you have fetch conditionally. Because if you make request on the server-side you have to put absolute url but if you are on the browser you use relative path. another thing that you have to be aware, when you make a request you have to attach the cookie automatically.
in your example you are trying to make the request from _app.js. Next.js uses the App component to initialize the pages. So if you want to show some secret data on the page, do it on that page. _app.js is wrapper for all other components, anything that you return from getInitialProps function of _app.js will be available to all other components in your application. But if you want to display some secret data on a component upon authorization, i think it is better to let that component to fetch the data. Imagine a user logins his account, you have to fetch the data only when user logged in, so other endpoints that does not need authentication will not access to that secret data.
So let's say a user logged in and you want to fetch his secret data. imagine you have page /secret so inside that component I can write like this:
Secret.getInitialProps = async (ctx) => {
const another = await getSecretData(ctx.req);
return { superValue: another };
};
getSecretData() is where we should be fetching our secret data. fetching actions are usually stored in /actions/index.js directory. Now we go here and write our fetching function:
// Since you did not mention which libraries you used, i use `axios` and `js-cookie`. they both are very popular and have easy api.
import axios from "axios";
import Cookies from "js-cookie";
//this function is usually stored in /helpers/utils.js
// cookies are attached to req.header.cookie
// you can console.log(req.header.cookie) to see the cookies
// cookieKey is a param, we pass jwt when we execute this function
const getCookieFromReq = (req, cookieKey) => {
const cookie = req.headers.cookie
.split(";")
.find((c) => c.trim().startsWith(`${cookieKey}=`));
if (!cookie) return undefined;
return cookie.split("=")[1];
};
//anytime we make request we have to attach our jwt
//if we are on the server, that means we get a **req** object and we execute above function.
// if we do not have req, that means we are on browser, and we retrieve the cookies from browser by the help of our 'js-cookie' library.
const setAuthHeader = (req) => {
const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");
if (token) {
return {
headers: { authorization: `Bearer ${token}` },
};
}
return undefined;
};
//this is where we fetch our data.
//if we are on server we use absolute path and if not we use relative
export const getSecretData = async (req) => {
const url = req ? "http://localhost:3000/api/v1/secret" : "/api/v1/secret";
return await axios.get(url, setAuthHeader(req)).then((res) => res.data);
};
this is how you should implement fetching data in next.js

Categories