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!
Related
I have the below two guards in NestJS(one for api key based authentication and another for token based authentication).The ApiKeyGuard is the top priority.I want to implement a system where if anyone has a key it will not check the other guard.Is there any way I can make the AuthGuard optional based on whether the first Guard passed in cases where there is a ApiKeyGuard?
// Can be accessed with token within app as well as third party users
#UseGuards(ApiKeyGuard, AuthGuard)
#Get('/get-products')
async getProducts(): Promise<any> {
try {
return this.moduleRef
.get(`appService`, { strict: false })
.getProducts();
} catch (error) {
throw new InternalServerErrorException(error.message, error.status);
}
}
// Only to be accessed with token within app
#UseGuards(AuthGuard)
#Get('/get-users')
async getUsers(): Promise<any> {
try {
return this.moduleRef
.get(`appService`, { strict: false })
.getUsers();
} catch (error) {
throw new InternalServerErrorException(error.message, error.status);
}
}
The below guard is used to check for api key based authentication
api-key.guard.ts
#Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(private readonly apiKeyService: ApiKeyService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const key = req.headers['X-API-KEY'] ?? req.query.api_key;
return this.apiKeyService.isKeyValid(key);
}
The below guard is used to check for token based authentication
authentication.guard.ts
#Injectable()
export class AuthGuard implements CanActivate, OnModuleInit {
constructor(private readonly moduleRef: ModuleRef) {}
onModuleInit() {}
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
// Get request data and validate token
const request = context.switchToHttp().getRequest();
if (request.headers.authorization) {
const token = request.headers.authorization.split(' ')[1];
const response = await this.checkToken(token);
if (response) {
return response;
} else {
throw new UnauthorizedException();
}
} else {
throw new UnauthorizedException();
}
} catch (error) {
throw new UnauthorizedException();
}
}
}
What I did was using #nestjs/passport and using the AuthGuard and making custom PassportJS strategies. I had a similar issue, and looked for a way to accomplish this without using some "magic". The documentation can be found here.
In the AuthGuard, you can add multiple guards. It's a bit hidden away in the documentation, although it is very powerful. Take a look here, especially the last line of the section, it states:
In addition to extending the default error handling and authentication logic, we can allow authentication to go through a chain of strategies. The first strategy to succeed, redirect, or error will halt the chain. Authentication failures will proceed through each strategy in series, ultimately failing if all strategies fail.
Which can be done like so:
export class JwtAuthGuard extends AuthGuard(['strategy_jwt_1', 'strategy_jwt_2', '...']) { ... }
Now, back to your example, you've to create 2 custom strategies, one for the API key and one for the authorization header, and both these guards should be activated.
So for the API strategy (as example):
import { Strategy } from 'passport-custom';
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy } from 'passport-custom';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
#Injectable()
export class ApiStrategy extends PassportStrategy(Strategy, 'api-strategy') {
constructor(private readonly apiKeyService: ApiKeyService) {}
async validate(req: Request): Promise<User> {
const key = req.headers['X-API-KEY'] ?? req.query.api_key;
if ((await this.apiKeyService.isKeyValid(key)) === false) {
throw new UnauthorizedException();
}
return this.getUser();
}
}
Do something similar for your other way of authenticating, and then use the Passport guard as follows:
#UseGuard(AuthGuard(['api-strategy', 'other-strategy'])
This way, the guard will try all strategies (in order) and when all of them fail, your authentication has failed. If one of them succeeds, you're authenticated!
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 have an Angular 9 web app and so far I have made it work quite nicely with Firestore. I have a form for adding user which saves the user to Firestore correctly but without adding them to Authenticated users, the add feature will be removed when I fix the issue below.
users.component.ts:
this.userService.addUser(user);
and in the userService I call:
async addUser(user: User) {
await this.firestore.collection('users').doc(user.email).set(user);
}
The problem is when I want to register a new user with FirebaseAuthentication, the user data does not get saved in users collection. When user clicks on Register in register.component.ts I call:
register() {
this.authenticationService
.register(this.form.controls['email'].value, this.form.controls['password'].value)
.then(() => {
this.userService.addUser({
nickname: this.form.controls['nickname'].value,
email: this.form.controls['email'].value,
role: 'Customer',
});
})
.catch((error) => {
console.log(error);
});
}
The register method in authenticationService.ts:
register(email: string, password: string) {
return this.angularFireAuth.auth.createUserWithEmailAndPassword(email, password);
}
I have tried different approaches, using Promises, async / await, calling directly from register method in authenticationService.ts, using collection('users').add instead of set, using uid I get as a response as document uid instead of email.
I'm inclined to think that there is some kind of rollback mechanism, since I have subscribed to data$:
this.firestore
.collection<User>(this.path)
.snapshotChanges()
.pipe(
takeUntil(this.destroy$),
map((items) => {
return items.map((item) => {
const data = item.payload.doc.data() as User;
return {
...data,
};
});
})
)
.subscribe((data) => {
this.users$.next(data);
});
in register.component.ts ngOnInit:
this.userService.users$.subscribe((data) => {
console.log(data);
});
Which gave me some insight:
The subject triggers twice instantly, with 4 elements and then 3.
users.component.ts
constructor(private userService: UserService, public dialog: MatDialog) {}
register.component.ts
constructor(
private userService: UserService,
private formBuilder: FormBuilder,
private authenticationService: AuthenticationService
) {}
Both components are part of the same module, but for some reason if I navigate to users.component.ts and then to register.component.ts saving user works! It gets added to both Authenticated users and Firestore users collection.
I am not getting any errors at any point.
So I really have no idea what might be causing it to behave differently, but I need it to work from register.component.ts without first navigating to users.component.ts.
EDIT:
My rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// This rule allows anyone on the internet to view, edit, and delete
// all data in your Firestore database. It is useful for getting
// started, but it is configured to expire after 30 days because it
// leaves your app open to attackers. At that time, all client
// requests to your Firestore database will be denied.
//
// Make sure to write security rules for your app before that time, or else
// your app will lose access to your Firestore database
match /{document=**} {
allow read, write: if request.time < timestamp.date(2020, 8, 6);
}
}
}
If you see a rollback like this, it typically means that the server rejected the write operation based on the security rules you have specified for the database. So it seems like the user doesn't have permission to write.
You'll have to modify the security rules of the database to allow the user to write their profile data.
You'll have to manually add the created user to your collection.
After createUserWithEmailAndPassword() is sucesfully executed, you should call a custom function that get's the User Object and saves it to Firestore.
example:
register(email: string, password: string) {
let rs = this.angularFireAuth.auth.createUserWithEmailAndPassword(email, password);
return this.saveUser(rs.user)
}
saveUser(user){
const userRef: AngularFirestoreDocument<User> = this.angularFistore.doc(`users/${user.email}`);
userRef.get().subscribe(snapDoc =>{
if(!snapDoc.exists){
const data; //Initialize
//do anything else
return userRef.set(data, { merge: true })
})
}
So I am currently using NestJS extensively in my organization. And for authentication purposes we are using our own guards. So my question is that can anyone please guide me if there any way to pass data from guard to the controller, other than response.locals of expressjs? This is creating an hard dependency on the framework and I don't want that at this moment.
TIA.
The only ways possible to pass data from a Guard to a Controller is to either attach the data to a field on the request or to use some sort of metadata reflection, which may become more challenging than it is worth.
In your guard you could have a canActivate function like
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const req = context.switchToHttp().getRequest();
if (/* some logic with req */) {
req.myData = 'some custom value';
}
return true;
}
And in your controller you could then pull req.myData and get the some custom value string back.
Instead of using the Guard, you can create your custom decorator to get the data:
export const Authorization = createParamDecorator((_, request: any) => {
const { authorization: accessToken } = request.headers;
try {
const decoded = jwt.verify(accessToken, process.env.JWT_HASH);
return pick(decoded, 'userId');
} catch (ex) {
throw new InvalidToken();
}
});
export interface AuthUser {
userId: string;
}
And pass to your controller like this:
#Post()
createFeedback(
#Body() body: FeedbackBody,
#Authorization() user: AuthUser,
): Promise<Feedback> {
body.userId = user.userId;
return this.feedbackService.feedback(body, user);
}
This can act as a guard because when your token is invalid, it will throw an exception
as other guys said you can pass data on the request object on the guard but receiving them through the #Req decorator is not fancy, especially if you don't have any other use for the request object. You can create a custom decorator that will retrieve the data you want and inject them into any controller
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
}
}
}