I am trying to test csrf protection in my nest application as per the nest documentation by using csurf but when I try to test using Postman its always showing invalid csrf.
I am using express platform which is default.
As per the documentation on csurf I am using express-session
I am applying csrf globally.
I try localhost:3000/csrf & get the token
then I go to localhost:3000/process in postman and add the token but get invalid csrf token
screenshot of how sending csrf in postman
Also tried sending _csrf as json { "_csrf": "----token_here-----" } but no go.
main.ts
import { ValidationPipe } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import helmet from 'helmet';
import * as session from 'express-session';
import * as csrf from 'csurf';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use([
helmet(),
session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {},
}),
csrf(),
]);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
await app.listen(3000);
}
bootstrap();
app.controller.ts
import { Controller, Get, Post, Req } from '#nestjs/common';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getHello(#Req() req): string {
return this.appService.getHello();
}
#Get('csrf')
getCsrf(#Req() request: any): string {
return request.csrfToken();
}
#Post('process')
postProcess() {
return "hello there, I am processing";
}
}
Related
index.ts
import express, { Express } from 'express';
import dotenv from 'dotenv';
import { DataSource } from 'typeorm';
import cors from 'cors';
import bodyParser from 'body-parser';
import { Task } from './src/tasks/tasks.entity';
import { tasksRouter } from './src/tasks/tasks.router';
// Instantiate express app
const app: Express = express();
dotenv.config();
//Parsing incoming request
app.use(bodyParser.json());
//Use CORS install types as well
app.use(cors());
// Create Databse Connection
export const AppDataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DB,
entities: [Task],
synchronize: true,
logging: true,
});
//Define Server Port
const port = process.env.PORT;
AppDataSource.initialize()
.then(() => {
// Start listening Request on defined port
app.listen(port);
console.log('Data Source has been initialized');
})
.catch((err) => console.log(err));
//create a Route
app.use('/', tasksRouter);
tasks.router.ts
import { Request, Response, Router } from 'express';
import { validationResult } from 'express-validator';
import { tasksController } from './tasks.controller';
import { createValidator } from './tasks.validator';
export const tasksRouter: Router = Router();
tasksRouter.get('/tasks', tasksController.getAll);
tasksRouter.post(
'/tasks',
createValidator,
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
//#ts-ignore
async (req: Request, res: Response) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res
.json({
errors: errors.array(),
})
.status(400);
}
},
);
tasks.controller.ts
import { AppDataSource } from '../../index';
import { Task } from './tasks.entity';
import { instanceToPlain } from 'class-transformer';
import { Request, Response } from 'express';
class TasksController {
constructor(private taskRepository = AppDataSource.getRepository(Task)) {}
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
//#ts-ignore
public async getAll(req: Request, res: Response): Promise<Response> {
let allTasks: Task[];
try {
allTasks = await this.taskRepository.find({
order: {
date: 'ASC',
},
});
allTasks = instanceToPlain(allTasks) as Task[];
res.json(allTasks).status(200);
} catch (_errors) {
return res.json({ error: 'Internal Server Error' }).status(500);
}
}
}
export const tasksController = new TasksController();
Hi everyone, I wish you all a very Happy New Year.
I have a question, While accessing the Task Entity, I have been getting error in the tasks.controller.ts file and
TypeError: Cannot read properties of undefined (reading
'getRepository')
What can be the actual Issue? What difference soes it make if I Instantiate the class in tasks.controller.ts file instead of the tasks.router.ts file? coz it is working if I Instantiate the TasksController class inside the route
I try to use a NestJS backend with a Nginx reverse proxy.
I have coded an authentication part in my NestJS backend.
My problem is that when I used my frontend / backend in local mode, all is ok.
When I use it through Nginx, I always retrieve a 401 error.
I think it’s due to the LocalStrategy in NestJS
Here is the part in the local.strategy.ts file
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthService } from '../auth.service';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
usernameField: 'userLogin',
passwordField: 'userPassword',
});
}
async validate(userLogin: string, userPassword: string): Promise<any> {
const user = await this.authService.validateUser(userLogin, userPassword);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Here is the part in the app.controller.ts file
#Public()
#UseGuards(LocalAuthGuard)
#Post('auth/login')
async login(#Request() req) {
return this.authService.login(req.user);
}
But I don’t know how to change it.
If somebody have an example it build be great.
Thanks in advance.
I've tested with curl locally on server (without nginx).
I saw my error (mysql access to test the user), it was not due to nginx configuration.
I am trying to figure out how to authenticate my request to firestore.
I am using https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=<API_KEY> for both which returns me an idToken of the user.
I do authentication on my routes which work but exposes my firestore still, thus me switching over to using security rules but I can't seem to authenticate any request.
I am using express to handle routes to firestore using this format locally:
GET http://localhost:5001/<PROJECT_ID>/us-central1/database/users/
Rules: allow read, write: if true;
GET https://firestore.googleapis.com/v1/projects/<PROJECT_ID>/databases/(default)/documents/users
content-type: application/json
Response: 200 (And I see all the documents)
Rules: allow read, write: if request.auth != null;
GET https://firestore.googleapis.com/v1/projects/<PROJECT_ID>/databases/(default)/documents/users
Authorization: Bearer {{idToken}}
content-type: application/json
Response: {
"error": {
"code": 403,
"message": "Missing or insufficient permissions.",
"status": "PERMISSION_DENIED"
}
}
MORE DETAILED INFO
The code below works, but with firebase other way of getting the data, it bypasses this and will only limit based on security rules.
index.ts
import * as functions from 'firebase-functions';
import * as express from 'express';
import * as cors from 'cors';
import isAuthenticated from './components/middleware/authenticated';
import isAuthorized from './components/middleware/authorized';
import users_all from './users/controllers/all';
const route = express();
route.use(cors({ origin: true }));
route.get('/users', isAuthenticated, isAuthorized({ hasRole: ['admin', 'manager'] }), users_all);
exports.database = functions.https.onRequest(route);
users_all
import { Request, Response } from "express";
import sentry from '../../components/reporting/sentry';
import Fireapp from '../../components/firebase/fireapp';
Fireapp
const all = async (req: Request, res: Response) => {
try {
let profiles: any = [];
/** Retrieve the exact document reference */
const reference = Fireapp.firestore().collection('users').get()
.then((documents: firebase.firestore.QuerySnapshot) => {
documents.docs.forEach((doc: firebase.firestore.DocumentData) => { profiles.push(doc.data()) });
return profiles;
});
return Promise.all([reference]).then((response: any) => {
res.status(200).send(profiles);
}).catch((error: any) => { throw error });
} catch (error) {
sentry(error, { service: '[GET ALL USER]', level: 'moderate', message: error.message });
res.status(400).send(error.message)
}
}
export default all;
I'm trying to implement RS256 JWT tokens in nestjs backend. I followed the example provided in nestjs documentation.
In my module I register the JwtModule with my private key:
#Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secretOrPrivateKey: extractKey(`${process.cwd()}/keys/jwt.private.key`),
signOptions: {
expiresIn: 3600,
},
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy, HttpStrategy],
})
export class AuthModule {}
I'm able to call auth/token endpoint and get the token but when I try to access guarded endpoint I always get 401.
Below you can find my custom JwtStrategy:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: extractKey(`${process.cwd()}/keys/jwt.public.key`),
});
}
async validate(payload: JwtPayload) {
console.log('JwtStrategy');
const user = await this.authService.validateUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
And guarded endpoint:
#Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
#Get('token')
async createToken(): Promise<any> {
return await this.authService.createToken();
}
#Get('data')
#UseGuards(AuthGuard())
findAll() {
console.log('Guarded endpoint');
// This route is restricted by AuthGuard
// JWT strategy
}
}
I assume that when I call the auth/data I should see in the console at least the "JwtStrategy" string that I log in the validate method. Unfortunately it never shows up. Why the validate method is never called?
Please find the codesandbox below
You have to specify RS256 as the algorithm for in both the JwtModule and the JwtStrategy:
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: publicKey,
algorithms: ['RS256'],
^^^^^^^^^^^^^^^^^^^^^^
});
and
JwtModule.register({
secretOrPrivateKey: privateKey,
signOptions: {
expiresIn: 3600,
algorithm: 'RS256',
^^^^^^^^^^^^^^^^^^^
},
}),
Not sure if it works but you can try this
#UseGuards(AuthGuard('jwt'))
above your protected route.
It's quite possible that the public key and/or private key files were not generated in RS256 format.
I'd recommend trying the following:
https://gist.github.com/ygotthilf/baa58da5c3dd1f69fae9
I kept getting
res.jwt is not a function
I have installed jwt-express and import it like so
import jwt from 'jwt-express'
This is my auth.js
import Account from '../services/account.js'
import env from 'dotenv'
import _ from 'lodash'
const dotenv = env.config();
module.exports = {
/**
* Process the user login, generating and returning a token if successful.
*
* #return {res}
*/
async login(req, res, next) {
try {
let origin = req.headers.origin;
let accounts = await Account.getAccounts();
let account = _.find(accounts, {
'email_address' : req.body.username,
'password' : req.body.password
});
if (!account) {
res.send('Username/Password Wrong');
}
// res.send(account);
let authentication = res.jwt({
'email': account.email_address,
'id': account.account_id
});
res.send(authentication);
} catch (error) {
next(error)
}
}
};
index.js
import express from 'express'
import favicon from 'serve-favicon'
import path from 'path'
import bodyParser from 'body-parser'
import bluebird from 'bluebird'
import jwt from 'jwt-express'
import env from 'dotenv'
//Controllers
import fortinetController from './controllers/fortinet'
import authController from './controllers/auth.js'
//Logger
import logger from './config/logger.js'
//Constant
const router = express.Router();
const app = express();
const PORT = 3000;
const dotenv = env.config();
Promise = bluebird;
app.use(bodyParser.urlencoded({extended: true }));
app.use(bodyParser.json());
app.use(router)
app.use(express.static('public'))
app.use(favicon(path.join(__dirname,'public','favicon.ico')))
app.use(jwt.init('CARWASH', {cookies: false }));
router.get('/', (req,res) => {
res.send('Welcome to the backend provisioning daemon to program FortiManager')
});
router.post('/login', authController.login);
//Fortinet
router.post('/fortinet/login', fortinetController.login);
router.post('/fortinet/getSessionTimeOut', fortinetController.getSessionTimeOut);
router.post('/fortinet/logout', fortinetController.logout);
//Error handling function
app.use((err,req,res,next) => {
console.error(err.stack)
res.status(500).send(`Red alert! Red alert!: ${err.stack}`)
logger.error(`${req.method} ${req.url} - ${err.log || err.message}`);
});
app.listen(PORT, () => {
console.log(`Your server is running on ${PORT}`)
}
);
How can I debug this?
Update
I've tried to add this
console.log(jwt);
I got
[nodemon] 1.17.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `babel-node ./index.js`
{ active: [Function: active],
clear: [Function: clear],
create: [Function: create],
init: [Function: init],
options:
{ cookie: 'jwt-express',
cookieOptions: { httpOnly: true },
cookies: false,
refresh: true,
reqProperty: 'jwt',
revoke: [Function: revoke],
signOptions: {},
stales: 900000,
verify: [Function: verify],
verifyOptions: {} },
require: [Function: require],
valid: [Function: valid] }
Your server is running on 3000
You are not configuring express-jwt correctly.
You are using express-jwt completely wrong.
Let's walk through each point.
I'm not sure why you think you need to call jwt.init(...) when the documentation here states to simply do: jwt(...). So you'll need to make the following changes:
Change
app.use(jwt.init('CARWASH', {cookies: false }));
To
app.use(jwt({secret: 'CARWASH'}));
There does not exist a cookies options, not sure where you got that from.
Now express-jwt will only handle verification of the JWT. It does not generate JWT for as you are trying to do in your auth.js.
In order to generate JWT, you will need another module: jsonwebtoken. You will then use the module in your auth.js like so:
import jwt from "jsonwebtoken";
// ...
module.export = {
async login(req, res, next) {
try {
// ... auth logic omitted
// Here we generate the JWT
// Make sure the JWT secret is the SAME secret you used for express-jwt
let authentication = jwt.sign({
'email': account.email_address,
'id': account.account_id
}, 'CARWASH');
res.send(authentication);
}
catch (error) {
next(error);
}
}
}