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?
Related
I'am developing a Nestjs micro-service and need to parse the incoming JWT in the request to the outgoing http requests, but seems like there is no cleaner way to do that, or am I missing something?
This is what I have done so far
Note: Using nestjs axios http client
user.contoller.ts
#Controller('/users')
export class UserController {
constructor(private userService: userService,
private vehicleService: VehicleService){}
#Post()
create(#Req() req: Request, #Body() userData: userData) {
// I can get the JWT token from here
// fetch some data from different micro-service
const d = this.vehicleService.getVehicleById(userData.vehicleId, req.headers['authorization'])
// save user
}
}
vehicle.service.ts
#Injectable()
export class VehicleService {
basePath = http://ms-vehicles/api/v1/vehicles;
constructor(private httpService: HttpService) {}
getVehicleById(vehicleId: string, token: string) {
return this.httpSerice.get(this.basePath + '/' + vehicleId,
{headers: {Authorization: token});
}
}
We can intercept the outgoing http calls using axios interceptor, but How can I set the correct token from there, without parsing the token through the the controller and service as a parameter
Found below solution in the stack overflow and I'am not sure this is the right way to do this.
interceptor
#Injectable()
export class HttpServiceInterceptor implements NestInterceptor {
constructor(private httpService: HttpService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// ** if you use normal HTTP module **
const ctx = context.switchToHttp();
const token = ctx.getRequest().headers['authorization'];
// ** if you use GraphQL module **
const ctx = GqlExecutionContext.create(context);
const token = ctx.getContext().token;
if (ctx.token) {
this.httpService.axiosRef.defaults.headers.common['authorization'] =
token;
}
return next.handle().pipe();
}
}
Register interceptor in main module as global one
#Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: HttpServiceInterceptor,
},
],
})
export class AppModule {}
if I use above solution, can we guarantee that someone else's token will not be used in other requests, because this common headers available for any places where we used httpService? or am I worong?
I create an authentication middleware in NestJs like below:
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor() {}
async use(req: any, res: any, next: () => void) {
const authHeaders = req.headers.authorization;
if (authHeaders) {
//some logic etc.
//req.user = user;
next();
} else {
throw new UnathorizedException();
}
}
}
... where I get from headers - an auth token, decode it and check if this user is correct and exists in database, if he exists then i set user object into req.user. And now I have a question, how to get this req.user in my services and use in business logic? I need to get id from req.user but I do not know how.
I know that I can do this by using #Req() request in controller parameters and pass this request into my function, but I do not want it, cause is (for me) a ugly practice. So, how to get this req.user into my services?
thanks for any help!
Well, to get the user in the service you have two options:
use #Req() in the controller and pass it, as you have mentioned
Make your service REQUEST scoped and inject the request object into the service
Personally, I'd go with the former, as request scoping has its own pros and cons to start weighing and dealing with (like not being able to use the service in a passport strategy or a cron job). You can also just make the user optional, or bundle it into the body or whatever is passed to the service and then have access to it without it being an explicit parameter.
You can create a decorator to do it. Something like this
current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const CurrentUser = createParamDecorator(
(property: string, ectx: ExecutionContext) => {
const ctx = ectx.getArgByIndex(1);
return property ? ctx.req.user && ctx.req.user[property] : ctx.req.user;
},
);
example.controller.ts
#ApiTags('example')
#Controller('example')
export class ExampleController {
constructor(private readonly exampleService: ExampleService) {}
#Get('/')
public async doSomething(#CurrentUser() user: YourUserClassOrInteface,): Promise<any> {
return this.exampleService.exampleFunction(user.id);
}
}
example.service.ts
export class ExampleService {
constructor() {}
public async exampleFunction(id: string): Promise<void> {
console.log('id:', id);
return;
}
}
IMPORTANT: Injecting the Request in the services is not a good solution because it will make a new one in each endpoint request. That is why the Decorators are used. It will make it easy to work with needed data and do not hand over only the parameters that are needed instead of transferring the extra big request object.
Alternative solution(if you won't use request scoped injection): you can use async hooks. There is many libraries which simplify async hooks usage, like this one. You simply set your context in middleware:
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor() {}
async use(req: any, res: any, next: () => void) {
const authHeaders = req.headers.authorization;
if (authHeaders) {
//some logic etc.
//req.user = user;
Context.run(next, { user: req.user });
} else {
throw new UnathorizedException();
}
}
}
And then you can get user instance in any place in your code by simply calling Context.get()
You can define your own Request interface like this
import { Request } from 'express';
...
export interface IRequestWithUser extends Request {
user: User;
}
then just give the type of req parameter to IRequestWithUser.
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!
I'm implementing an Angular 2 service which gets JSON data from play framework server by http request.
http://localhost:9000/read returns JSON data like [{"id":1,"name":"name1"},{"id":2,"name":"name2"}].
And here's my Angular service code (from this tutorial https://angular.io/docs/ts/latest/guide/server-communication.html):
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Hero } from './hero';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class HttpService {
private heroesUrl = "http:localhost:9000/read"; // URL to web API
constructor (private http: Http) {}
getHeroes (): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
But the request in browser looks like this:
GET XHR http://localhost:4200/localhost:9000/read [HTTP/1.1 404 Not Found 5ms]
So Angular creates relative URL, when absolute URL is needed.
So could I...
1)Fix it in code.
2)Or make Angular 2 and Play run on same port.
3)Use JSONP or something else.
The reason to your relative url is simply because you have a typo, which causes this. Instead of
private heroesUrl = "http:localhost:9000/read";
it should be
private heroesUrl = "http://localhost:9000/read";
No other magic should probably not be needed here. You might run into a cors issue. But since this is a playground, and not actual development, in case of CORS you can enable CORS easily in chrome. But this is naturally NOT recommended in a real-life app. But for playing, that would do just fine.
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
}
}
}