Problem with Authentication chapter and the 19-auth sample - nestjs

I downloaded the 19-auth sample and add some console.log debug code to it, then found some problems.
The code in JwtAuthGuard is never executed: '2222222' was not printed to the console in the code below:
canActivate(context: ExecutionContext) {
console.log('22222222222');
// add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
When I changed the guard to JwtAuthGuard in the AuthController:
#get('data')
#UseGuards(JwtAuthGuard)
findAll(#Req() req) {
return req.user;
// this route is restricted by AuthGuard
// JWT strategy
}
the code in JwtAuthGuard was invoked, but in the canActivate function, I can't get the user info from request. and the canActivate function was called before the JwtStrategy?
Can someone explain how the code executing for the auth module, and how to get the user info in the JwtAuthGuard?
paste the latest code and console log here:
JwtStrategy
/**
* jwt passport 调用validate方法来判断是否授权用户进行接口调用
* #param payload
*/
async validate(payload: AuthPayload) {
Logger.log(`payload is ${JSON.stringify(payload)}`, 'JwtStrategy');
const user = await this.authService.validateUser(payload.id);
if (!user) {
throw new UnauthorizedException('不存在的用户信息');
}
return user;
}
JwtAuthGuard
canActivate(context: ExecutionContext) {
// add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
// this.accessPriv = this.reflector.get<string>('accessPriv', context.getHandler());
console.log('canActivate executed 111111111111111111');
return super.canActivate(context);
}
and the console log as below:
canActivate executed 111111111111111111
[Nest] 14080 - 2019-04-01 11:19 [JwtStrategy] payload is {"userName":"fanliang","id":"1","iat":1553772641,"exp":1554377441} +2286ms
it seems that the canActivate() function of JwtAuthGuard executed before the validate() function of JwtStrategy, but the user info was attached to the request after JwtStrategy validate().
what I want is to get the user info from request in the canActivate() of custom AuthGuard such like JwtAuthGuard

I have a somewhat solution that works for me.
Calling the super.canActivate before my own logic.
seems like the population of req.user triggered by it.
An example:
import { ExecutionContext, Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
import { Request } from "express";
#Injectable()
export class AuthGuardWithAllowSentry extends AuthGuard("jwt") {
public async canActivate(context: ExecutionContext) {
// that code will call the passport jwt
const origCanActivate = await super.canActivate(context);
// now we have request.user!
const http = context.switchToHttp();
const request = http.getRequest<Request>();
console.log(request.user)
if (request.header("X-Sentry-Token") === "blablabla") {
if (request.method === "GET" && request.path.endsWith(".map")) {
return true;
}
}
// some random logic
return request.user.roles.includes("admin")
}
}
it feels for me more like a workaround than a real thing.

I agree that the 19-auth sample is a little bit confusing to follow. This is mainly because it includes the JWTAuthGuard (as a reference for building custom guards) but it is never actually used. Instead, the original use of plain AuthGuard is already set up to provide JWT functionality. However, both guards leverage the JWTStrategy. If you want to understand this better, you could try updating your AuthController:
#Get('data')
#UseGuards(AuthGuard())
findAll() {
// this route is restricted by AuthGuard
// JWT strategy
return {
message: 'Successfully passed AuthGuard',
};
}
#Get('custom-jwt')
#UseGuards(new JwtAuthGuard())
// this route is restricted by JWTAuthGuard custom
// JWT strategy
customJwt() {
return {
message: 'Successfully passed JWTAuthGuard',
};
}
The important part is that in order to get past either guard, you must send the request with the Authorization header properly set to the token that's returned from the token endpoint.
For example: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIiwiaWF0IjoxNTU0MDUyNDczLCJleHAiOjE1NTQwNTYwNzN9.3Q8_FC-qFXk1F4KmMrHVSmmNGPAyHdt2myr5c18_E-U
I find it easiest to use a tool like Postman or Insomnia for constructing requests and setting Headers, etc but you could also use CURL. Once you've set the Authorization header with a valid token you'll be able to hit both of the guarded endpoints. If you put a console.log in the JWTStrategy you'll see that both guards end up using the validate method to retrieve the user correctly.

Related

NestJS Is a good aproach using DTO classes in request object?

I'm learning NestJS and now I'm working in a simple authentication application, at this point I configured global pipes to validations and I'm using dto classes for example to validate #Body() fields. I don't know if I can use DTO to validate #Request fields sent from login endpoint.
import { Controller, Post, Request, UseGuards } from '#nestjs/common';
import { AuthService } from './auth.service';
import { AuthGuard } from '#nestjs/passport';
#Controller()
export class AuthController {
constructor(private authService: AuthService) {}
#UseGuards(AuthGuard('local'))
#Post('auth/login')
async login(#Request() req: reqDto /* I would like to use DTO for validations*/) {
return this.authService.login(req.user);
}
}
PS: I'm using DTO to validate SingUp body In UserController.
#Post('/signup')
createUser(#Body() createUserDto: CreateUserDto) {
return this.userService.createUser(createUserDto);
}
#Request() and #Headers() are two decorators that skip validation via pipes. You can make a custom request decorator that does get called via pipes, but annotating the request object would be a lot of work. What would be better is to create a decorator that gets just the data you need off the request, and then make a DTO for that object and validate that as necessary

JWT Authentication with NextJS

I've searched around a bit, but have not found any clear, up-to-date, answers on this topic.
I'm trying to implement JWT authentication in my NextJS application. The following is what I have so far.
/login endpoint that will (1) check that the user/pass exists and is valid, and (2) create a JWT token based on a private RS256 key.
Created a middleware layer to verify the JWT
The creation of the JWT is fine - it works perfectly well reading the key from the file-system and signing a JWT.
However, I've run into the problem of the middleware being unable to use node modules (fs and path) because of the edge runtime (read here). This makes it so I'm unable to read the public key from the FS.
What is the proper way to verify a JWT token on every request? I've read that fetching from middleware is bad practice and should be avoided. All other reference on this topic (that I found) either uses a "secret" instead of a key (and can therefor be put into process.env and used in middleware) or glosses over the fact (1). Or should I just create a separate express application to handle JWT creation/verifying?
What I do is add a file (_middleware.tsx) within pages. This ensures the file runs each page load but is not treated as a standard page. In this file lives an Edge function. If the user is not signed in (no JWT in cookies) and tries to access a protected page, he is immediately redirected to /signin before the server is even hit.
import { NextResponse } from "next/server";
const signedInPages = ["/admin", "/members "];
export default function middleware(req) {
if (signedInPages.find((p) => p === req.nextUrl.pathname)) {
const { MY_TOKEN: token } = req.cookies;
if (!token) {
return NextResponse.redirect("/signin");
}
}
}
If you signed the token from
import jwt from "jsonwebtoken";
then you can use same jwt for verifying token inside a middleware function. you create _middleware.js inside pages directory and middleware will run for each request before request hits the endpoint:
import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";
export async function middleware(req, ev) {
const token = req ? req.cookies?.token : null;
let userId=null;
if (token) {
// this is how we sign= jwt.sign(object,secretKey)
// now use the same secretKey to decode the token
const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
userId = decodedToken?.issuer;
}
const { pathname } = req.nextUrl;
// if user sends request to "/api/login", it has no token. so let the request pass
if (
pathname.includes("/api/login") || userId
) {
return NextResponse.next();
}
if (!token && pathname !== "/login") {
return NextResponse.redirect("/login");
}
}
you send the token in the post request header.
{Authorization: 'Bearer ' + token}
And then verify that token on the server, before sending data back
To verify the token you create a middleware function, then use that function on any route you want to portect
router.get("/", middlewarefunction, function (req: any, res: any, next: any) {
res.send("/api")
})

Calling an onCall function in Firebase Functions using AngularFire results in a 404 error, while returning the expected result

I have created a few onCall cloud functions using Firebase. These functions interact with Stripe through the API. When I use AngularFire, or more specifically, AngularFireFunctions to call these said cloud functions, I receive the error message A bad HTTP response code (404) was received when fetching the script. in Chrome developer console. Yet, the expected result is received with no problem and the Firebase console displays a 200 response with no error messages or warnings. The project is entirely hosted on Firebase.
The 404 error also does not display a file that it is connected to in the console as such errors typically do within that console.
UPDATE
I also feel it is relevant to include, the Stripe developer logs in the dashboard reflect no errors, but a successfull call upon checking.
I have also tried to remove the call to Stripe in the cloud function and simply only return a string return 'The customer ID is:'+ ${data.customerId}+'. Thank you.' and still received the same error message.
I have also tried this solution, https://github.com/angular/angularfire/issues/1933#issuecomment-432910986 with the following code being placed inside app.module.ts however, am unable to find where FunctionsRegionToken would be defined to be able to import it.
providers: [
{ provide: FunctionsRegionToken, useValue: 'us-central1' }
]
Although, I'm not sure how changing the region to the same region the function is being called from currently would make any difference.
When you explore the Network tab of the developer console and visit the page that calls the function, you see that something is trying to call http://localhost:4200/firebase-messaging-sw.js which doesn't exist. The amount of calls to this file and the errors in the console coincide with each other which leads me to believe they are related.
END OF UPDATE
I have tried to add CORS to my cloud function (and am using it in onRequest functions), I've tried rewriting my cloud function, and even tried changing the client side function that calls the onCall to no avail. The only way to remove the error is to remove the call to the function, thus I've narrowed it down to something with the AngularFireFunctions.
What I am using and the versions
Angular V13
Firebase 9.6.7
Angular Fire 7.2.1
Node 16.13.1
What follows is my code, broken up into sections.
Cloud function
const cors = require('cors')({origin: true});
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
const FieldValue = require('firebase-admin').firestore.FieldValue;
admin.initializeApp();
const firebaseApp = admin.app();
const firebaseDB = firebaseApp.firestore();
const Stripe = require('stripe');
const stripe = Stripe(functions.config().stripe.key);
export const retrieveCustomer = functions.https.onCall( async(data) => {
if(data.customerId) {
const customer = await stripe.customers.retrieve(data.customerId);
if(customer) {
return customer;
} else {
throw new functions.https.HttpsError('unknown', 'An unknown error occurred, please try again.');
}
} else {
throw new functions.https.HttpsError('invalid-argument', 'A customer ID must be provided.');
}
});
Angular Service
import { Injectable } from '#angular/core';
import { AngularFirestore } from '#angular/fire/compat/firestore';
import { AngularFireFunctions } from '#angular/fire/compat/functions';
#Injectable({
providedIn: 'root'
})
export class BillingService {
constructor( private aff: AngularFireFunctions, private afs: AngularFirestore ) { }
RetrieveCustomer(customerId:string) {
const callable = this.aff.httpsCallable('retrieveCustomer');
return callable({
customerId: customerId
});
}
}
Angular Component
import { Component, OnInit, AfterContentInit } from '#angular/core';
import { BillingService } from 'src/app/shared/services/billing/billing.service';
#Component({
selector: 'app-billing-settings',
templateUrl: './billing-settings.component.html',
styleUrls: ['./billing-settings.component.css']
})
export class BillingSettingsComponent implements OnInit, AfterContentInit {
public stripeCustomer!: any;
constructor( private billingService: BillingService ) { }
ngOnInit(): void {
}
ngAfterContentInit(): void {
this.billingService.RetrieveCustomer('cus_LGRX8TPVF3Xh0w').subscribe((customer:any) => {
console.log(customer);
});
}
}

Nestjs JwtStrategy access to context

I have a UseGuard in my WebSocket. Actually, this guard is a JwtAuthGuard that extends AuthGuard('jwt'). The JwtAuthGuard has a Strategy class called JwtStrategy. In this class, I have a validate method. In HTTP-based requests I return payload in this method. Then nestjs attach the payload to the req. Here is my Strategy class:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private authConfigService: AuthConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: authConfigService.JWT_SECRET,
});
}
async validate(payload: any) {
return payload;
}
}
I want to have access to context within validate method in order to attach the payload to the WebSocket's body (or anything that I can have access to the payload). Any idea?
You don't need to make any modifications to your strategy class. Instead, you should modify your JwtAuthGuard's getRequest method (if you don't have one then you should make one) that returns an object that has a headers proeprty that is an object with a authorization property that is a string. Something like
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ws = context.switchToWs().getClient(); // possibly `getData()` instead.
return {
headers: {
authorization: valueFromWs(ws),
}
}
}
}
You can also make this work across different context types by using an if/else or a switch statement and returning the correct object based on the contextType from context.getType(). Whatever is returned from this getRequest method is where passport will end up attaching the user property, so it may make sense to return the entire client with these extra values.

nestjs save each request info without hitting database twice

I want to save each request (path, method, and userId) that comes to the server without having to hit the database twice, and also without messing up the main logic in services files with transactions.
Initially, I was trying to use an interceptor because it gets invoked after auth guards "which attaches the user to request" and before request handlers, but I faced two issues.
first, the fact that the interceptor will call the database to save a new record and then forward the request to handlers which will again hit DB again to handle the request. Secondly, It didn't work anyway because of dependancy injection problems.
code below is not working due to dependency errors as I mentioned, but It will give u an idea about what I need to acheive.
import { Injectable,
NestInterceptor,
Inject,
ExecutionContext,
CallHandler,
HttpException,
HttpStatus } from '#nestjs/common';
import { Observable } from 'rxjs';
import { getRepositoryToken } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { HistoryEntity } from '../../modules/history/history.entity';
#Injectable()
export class HistoryInterceptor implements NestInterceptor {
constructor(
#Inject(getRepositoryToken(HistoryEntity))
private readonly historyRepo: Repository<HistoryEntity>
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { user, path, method } = request
if (!user) {
throw new HttpException('something terrible happened!', HttpStatus.BAD_GATEWAY);
}
const history = this.historyRepo.create({
path,
userId: user.id,
});
this.historyRepo.save(history);
return next.handle();
}
}
PS. from a performance point of view, It would also be great to not halt the request execution to save these info in db, in other words, Is it ok to NOT use await in this particular situation? because essecntially this is a system related operation and so, Node [rocess shouldn't wait for this step to process and return response to client.
Thanks in advance.

Resources