I'm trying to bubble up an error that comes from the verify function in jsonwebtoken, however, it wraps it in another Internal 500 Status error, rather than an Unauthorized error, that I want it to be.
Most of the components are built in loopback-next authentication components. The class below is a provider for a class called AuthenticationStrategy. AuthenticationStrategy is just a class with a passport strategy that returns an Authentication Strategy, which is passed on the metadata of a route. In the class below (the provider of type Authentication strategy, value() is the function returned when calling the provider. The passport strategy verification function has to first be converted to an Authentication strategy first, then returned through the value function to the Provider. When value() is called, the verify function runs on the metadata of the route, in this case, the bearer token. The Function cb below is the same as the 'done' function in a normal passport strategy, and returns (error, object), respectively
import {AuthenticateErrorKeys} from '../error-keys';
import {RevokedTokenRepository, UserRepository} from '../../../repositories';
import {repository} from '#loopback/repository';
import {Strategy} from 'passport-http-bearer'
import {HttpErrors} from '#loopback/rest';
import {StrategyAdapter} from '#loopback/authentication-passport'
import {AuthenticationStrategy} from '#loopback/authentication'
import {inject, Provider} from '#loopback/context'
var verify = require('jsonwebtoken').verify
export const BEARER_AUTH_STRATEGY = 'bearer';
export class PassportBearerAuthProvider implements Provider<AuthenticationStrategy> {
constructor(
#repository(RevokedTokenRepository)
public revokedTokenRepository: RevokedTokenRepository,
#repository(UserRepository)
public userRepository: UserRepository,
){}
value(): AuthenticationStrategy {
const bearerStrategy = new Strategy(this.verify.bind(this));
return this.convertToAuthStrategy(bearerStrategy);
}
async verify (token: string, cb: Function){
try{
if (token && (await this.revokedTokenRepository.get(token))) {
throw new HttpErrors.Unauthorized(AuthenticateErrorKeys.TokenRevoked);
}
const userAuthToken = await verify(token, process.env.JWT_SECRET as string, {
issuer: process.env.JWT_ISSUER,
})
let authUser = await this.userRepository.getAuthUser(userAuthToken.id)
return cb(null, authUser)
}
catch(error) {
if (error.name && error.name === "JsonWebTokenError") {
return cb(new HttpErrors.Unauthorized(AuthenticateErrorKeys.TokenInvalid), null)
}
if (error.name && error.name === "TokenExpiredError") {
return cb(new HttpErrors.Unauthorized(AuthenticateErrorKeys.TokenExpired), null)
}
if (error.code && error.code === "ENTITY_NOT_FOUND") {
return cb(new HttpErrors.Unauthorized(`${AuthenticateErrorKeys.UserDoesNotExist}, id: ${error.entityId}`), null)
}
return cb(error, null)
}
}
// Applies the `StrategyAdapter` to the configured basic strategy instance.
// You'd better define your strategy name as a constant, like
// `const AUTH_STRATEGY_NAME = 'basic'`
// You will need to decorate the APIs later with the same name
convertToAuthStrategy(bearer: Strategy): AuthenticationStrategy {
return new StrategyAdapter(bearer,BEARER_AUTH_STRATEGY);
}
}
The sequence below is run every time someone makes an API request. If above the route, the route is decorated with #authenticate[BEARER_AUTH_TOKEN], the provider above is called, and the verify function is run on the metadata.
export class MySequence implements SequenceHandler {
constructor(
#inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
#inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
#inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
#inject(SequenceActions.SEND) public send: Send,
#inject(SequenceActions.REJECT) public reject: Reject,
#inject(AuthorizationBindings.AUTHORIZE_ACTION)
protected checkAuthorisation: AuthorizeFn,
#inject(AuthenticationBindings.AUTH_ACTION)
protected authenticateRequest: AuthenticateFn,
) {}
async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
const args = await this.parseParams(request, route)
const authUser = await this.authenticateRequest(request).catch(error => {
Object.assign(error, {statusCode: 401, name: "NotAllowedAccess", message: (error.message && error.message.message)? error.message.message: "Unable to Authenticate User" });
throw error
})
console.log(authUser)
const isAccessAllowed: boolean = await this.checkAuthorisation(
authUser && authUser.permissions,
request,
);
if (!isAccessAllowed) {
throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess);
}
const result = await this.invoke(route, args);
this.send(response, result);
} catch (error) {
this.reject(context, error);
}
}
}
But when it catches the error, the status is 500 and the 401 Unauthorized error is wrapped in the Internal Status error. How can I instead return the 401 error?
I have a lot more cases like this where the error is deeper, so I'm trying to have a rigorous implementation.
Related
I have no idea why, but for some reason the jwt.verify function is complaining about an invalid signature in only part of my application. To give you some background, I am trying to set up auth headers using the context function with Apollo Server (which I guess is irrelevant anyway as the jwt.verify function should work in any javascript code snippet the same way) as follows:
context: ({ req }) => {
const token = req.get('Authorization') || '';
if (!token) {
console.log('no token detected. returning null...');
return { user: null };
}
return {
user: verifyUser(token.split(' ')[1]),
};
},
The verifyUser function:
const verifyUser = (token: string) => {
try {
return jwt.verify(token, process.env.JWT_SECRET as Secret);
} catch (error: any) {
console.error('error: ', error.message);
return null;
}
};
Note that I’m following this guide and have renamed getUser to verifyUser.
In each graphql request, I am providing a Bearer token, like so:
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2F3ZXNvbWVhcGkuY29tL2dyYXBocWwiOnsicm9sZXMiOlsiYWRtaW4iXSwicGVybWlzc2lvbnMiOlsicmVhZDphbnlfYWNjb3VudCIsInJlYWQ6b3duX2FjY291bnQiXX0sImlhdCI6MTU4NjkwMDI1MSwiZXhwIjoxNTg2OTg2NjUxLCJzdWIiOiIxMjM0NSJ9.31EOrcKYTsg4ro8511bV5nVEyztOBF_4Hqe0_P5lPps"
}
But every time I make a graphql request (a query or mutation) in the graphql playground, I am getting an invalid token message in the catch block, so presumably the jwt.verify function is failing. I wondered whether the JWT I provided to the Bearer code above is wrong, but I do not think it is. I am getting it from an authenticateUser resolver:
export const authenticateUser = async (
_: undefined,
{ input: { email, password } }: any
): Promise<any> => {
try {
const user = await User.findByEmail(email);
if (!user) {
throw new HttpError(404, 'User not found. Please create an account.');
}
const correctPassword = await bcrypt.compare(password, user.hashedPassword);
if (!correctPassword) {
throw new HttpError(403, 'Wrong password for this account.');
}
// assign object methods to the user instance as objects retrieved from db don't have methods
Object.setPrototypeOf(user, User.prototype);
const token = user.signToken(); // see below
return { token, id: user._id };
} catch (error: any) {
throw new HttpError(error.statusCode, error.message);
}
}
The user.signToken() function comes from a User class:
signToken(expiry: number | string = 60 * 24): string {
const secret = process.env.JWT_SECRET + this.hashedPassword;
// { ...this } overcomes error `Expected "payload" to be a plain object`
const token = jwt.sign({ ...this }, secret, {
expiresIn: expiry,
});
return token;
}
The only difference is that I am passing in a hashed password with the secret argument. I’ve also noticed that the error does not occur when using jwt.sign instead of jwt.verify (like in this post), but I assume I have to use the verify function.
Does anyone know why my code may not be working? The guide I am following does not pass a hashed password into the secret argument.
What is the best practice how to organize code in Next.js api handlers? I saw this video and he puts all REST routes in two files:
pages/api/users/index.ts handles all operations that don't require id so GET /api/users - get all users and POST pages/api/users - create a new user
pages/api/users/[id].ts handles all operations that require id so GET api/users/1 - get user by id, PUT /api/users/1 - update user, and DELETE /api/users/1 - delete a user
This means a lot of code will go into just 2 files and handled by a switch case statement. How should all this code be organized?
Every case statement should have its own try catch block for handling database calls which is a lot of repetition, I could make single try catch around entire switch but that will wrap a lot of unnecessary code, and maybe each case needs different handling? I could put single try catch in higher order function and wrap each case block with it but I'm not sure that's nice either?
Also later I will need to protect some routes with withProtected and withRole middlewares but that wont be easy because now multiple api's are handled inside single handler call.
What is the best way to organize this? Is there already good complete example existing?
// pages/api/users/index.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { hash } from 'bcryptjs';
import prisma from 'lib/prisma';
/**
* POST /api/users
* Required fields in body: name, username, email, password
*/
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
): Promise<void> {
const { method, body } = req;
switch (method) {
case 'GET':
// get all
// try catch again?
const users = await prisma.user.findMany();
res.status(200).json({ users });
break;
case 'POST':
// create
try {
const { name, username, email, password: _password } = body;
// todo: validate...
const _user = await prisma.user.findFirst({
where: { email },
});
if (_user)
throw new Error(`The user with email: ${email} already exists.`);
const password = await hash(_password, 10);
const user = await prisma.user.create({
data: {
name,
username,
email,
password,
},
});
res.status(201).json({ user });
} catch (error) {
res.status(500).json({ error });
}
break;
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}
This is how I've done things. It also covers method not allowed
Working code example from my project
src/somewhere/globalAPICall.js
/**
*
* #param {http.IncomingMessage} req
* #param {http.ServerResponse} res
* #param {{[key: string]: () => Promise<void>}} actions
*/
export default async function globalAPICall(req, res, actions) {
try {
const method = req.method
// check an action exists with request.method else throw method not allowed
if (!Object.keys(actions).includes(method)) {
console.log('method not allowed')
throw new MyError('Method not allowed', 405)
}
// run the action matching the request.method
await actions[method]()
} catch(err) {
if (err instanceof MyError) {
res.status(err.code).send(err.message);
} else {
res.status(500).send("Internal server error");
}
}
}
src/pages/api/users.js
/**
*
* #param {http.IncomingMessage} req
* #param {http.ServerResponse} res
*/
export default async function handler(req, res) {
async function POST() {
const { email, password, username } = req.body;
if (!username) {
throw new MyError('Username required', 400)
}
await CreateUser(email, password, username)
res.status(201).send();
}
async function GET() {
const result = await ListUsers()
res.status(200).json(result);
}
await globalAPICall.js(req, res, {POST, GET})
}
How to handle the error so that if the user does not provide a token, then an UnauthorizedException is thrown.
At the moment I am getting this error:
{
"statusCode": 500,
"message": "Internal server error"
}
ts:
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
try {
const jwt = request.headers.authorization.split(' ')[1];
if (!jwt) {
throw new UnauthorizedException('Token is not provided.');
}
return this.jwtService.verify(jwt);
} catch (e) {
return false;
}
}
You can try to recreate auth module from the documentation.
Or try to console.log() on each line.
By default, JWT internal module works well. It can encode and decode all that you need automatically.
https://docs.nestjs.com/security/authentication
I use a middleware for this purpose. I'll share a basic version of it.
auth-middleware.ts
import {HttpStatus,Injectable,Logger,LoggerService,NestMiddleware,} from '#nestjs/common';
import { NextFunction } from 'express';
import { Request, Response } from 'express';
#Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(
private readonly authenticationService: AuthService,
// (I use Firebase auth. You can inject JWT service here instead)
private readonly logger: LoggerService, // Good'ol logger
) {}
public async use(req: Request, res: Response, next: NextFunction) {
// Checks if req has authorization header
const header = req.headers['authorization'];
if (!header) {
// If no headers are present, returns a 401.
// I use problem+json
// Thats why you are seeing more fields in the response instead of just a
// code and message
return res
.status(HttpStatus.UNAUTHORIZED)
.json({
title: 'Unauthorized',
detail: 'Invalid Token',
type: 'https://app-site.com/login',
status: HttpStatus.UNAUTHORIZED,
instance: 'login/null',
})
.setHeader('Content-Type', 'application/problem+json');
}
// Splitting "Bearer token" to ["Bearer","token"]
const token = header.split(' ')[1];
// Validating token with auth service
// It returns a "tuple" for me...you can have it return whatever you want
const [
authClaims, // Custom claims that is extracted from the JWT
result, // JWT Validation result (boolean)
authProviderUid, // Firebase UID
] = await this.authenticationService.verifyToken(token);
if (
!result || // If JWT is invalid
authClaims.accountStatus === AccountStatusList.Banned ||
authClaims.accountStatus === AccountStatusList.Suspended
) {
// You shall not pass
return res
.status(HttpStatus.UNAUTHORIZED)
.json({
title: 'Unauthorized',
detail: 'Invalid Token',
type: 'https://app-site.com/login',
status: HttpStatus.UNAUTHORIZED,
instance: 'login/null',
})
.setHeader('Content-Type', 'application/problem+json');
}
// Attaches the claims, result and UID with req for the next middleware(s)/controller
req['authResult'] = { authClaims, result, authProviderUid };
//Reassuring
this.logger.log('Token verified', AuthMiddleware.name);
// next function from express
next();
}
}
Next, In the module(s) your controllers are declared,
api.module.ts
import { MiddlewareConsumer, Module, NestModule, RequestMethod, } from '#nestjs/common';
#Module({
imports: [
//...
],
controllers: [
AuthController,
ProfileController,
SubscriptionController
],
providers: [
//...
],
})
export class ApiModule implements NestModule {
public async configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
// Exclude some paths
.exclude({ path: '/api/v1/auth/sign-up', method: RequestMethod.POST })
.forRoutes( // Your controller classes you want to run the middleware on
ProfileController,
SubscriptionController,
AuthController
);
}
}
How it works
Every request goes through the specified middleware (if path not excluded). If the request is unauthorized, throws an error before it reaches the controller.
If the request is at the controller, the request is authenticated. You have to take care of the authorization part with guards etc...
Authentication and Authorization are different.
I'd suggest to use middleware for authentication and guards for authorization.
Links :
NestJS Middleware Documentation
Problem Details
In my code base there are various Axios instances created using
axios.create()
because there are multiple base URLs used in my app. So per baseURL we have created a corresponding Axios instance.
Now in App.js file, I have included 2 interceptors
request interceptor
response interceptor
axios.interceptors.response.use(
config => {
return config;
},
error => {
if (error && error.response.status === 401) {
signOut();
}
return Promise.reject(error);
}
);
But all the API calls are by-passing the above-mentioned 2 interceptors.
Problem:
I want to use the above-mentioned interceptors as the Global interceptors for all the Axios instances in my project.
First Option - "Easier"
Just create your own "axios generator" function.
const createAxios = (baseURL) => {
const newInstance = axios.create({ baseURL });
newInstance.interceptors.response.use(
(config) => config,
(error) => {
if (error && error.response.status === 401) {
signOut();
}
return Promise.reject(error);
}
);
return newInstance;
}
Second Option - More Complicated
Personally I prefer this option, as I find it more tidy, and logically separated and divided (each "entity" stands by itself).
What i would probably do is create a BasicService Class which would look something like this:
import axios from 'axios';
class BasicService {
constructor(url) {
const options = {
baseURL: url,
// any options that you would want for all axios requests,
// like (proxy, etc...)
};
this.fetcher = axios.create(options);
// Your default config
this.fetcher.interceptors.response.use(
(config) => {
return config;
},
(error) => {
if (error && error.response.status === 401) {
signOut();
}
return Promise.reject(error);
}
);
}
}
then for each axios instance i would like to create, i would also create a class with all fetches. like:
const baseURL= '/users';
class UserService extends Service {
// GET Requests
async GetAll() {
return (await this.fetcher.get('/all')).data;
}
// POST Requests
async insertUser(userToInsert) {
return await this.fetcher.post(...)
}
}
const userService = new UserService(baseURL);
export default userService;
then in any file i would just import my wanted service and fetch it
import UserService from "/services/UserService";
UserService.getAll().then(...);
This helps you keep the same config for all axios instances while keeping your code as generic and clean as possible
I want to set an http status code in my GraphQL authentication query, depending on if auth attempt was successful (200), unauthorised (401) or missing parameters (422).
I am using Koa and Apollo and have configured my server like so:
const graphqlKoaMiddleware = graphqlKoa(ctx => {
return ({
schema,
formatError: (err) => ({ message: err.message, status: err.status }),
context: {
stationConnector: new StationConnector(),
passengerTypeConnector: new PassengerTypeConnector(),
authConnector: new AuthConnector(),
cookies: ctx.cookies
}
})
})
router.post("/graphql", graphqlKoaMiddleware)
As you can see, I have set my formatError to return a message and status but currently only the message is getting returned. The error message comes from the error that I throw in my resolver function.
For example:
const resolvers = {
Query: {
me: async (obj, {username, password}, ctx) => {
try {
return await ctx.authConnector.getUser(ctx.cookies)
}catch(err){
throw new Error(`Could not get user: ${err}`);
}
}
}
}
My only issue with this method is it is setting the status code in the error message and not actually updating the response object.
Does GraphQL require a 200 response even for failed queries / mutations or can I some how update the response objects status code? If not, How do I set the aforementioned error object status code?
Unless the GraphQL request itself is malformed, GraphQL will return a 200 status code, even if an error is thrown inside one of the resolvers. This is by design so there's not really a way to configure Apollo server to change this behavior.
That said, you could easily wire up your own middleware. You can import the runHttpQuery function that the Apollo middleware uses under the hood. In fact, you could pretty much copy the source code and just modify it to suit your needs:
const graphqlMiddleware = options => {
return (req, res, next) => {
runHttpQuery([req, res], {
method: req.method,
options: options,
query: req.method === 'POST' ? req.body : req.query,
}).then((gqlResponse) => {
res.setHeader('Content-Type', 'application/json')
// parse the response for errors and set status code if needed
res.write(gqlResponse)
res.end()
next()
}, (error) => {
if ( 'HttpQueryError' !== error.name ) {
return next(error)
}
if ( error.headers ) {
Object.keys(error.headers).forEach((header) => {
res.setHeader(header, error.headers[header])
})
}
res.statusCode = error.statusCode
res.write(error.message)
res.end()
next(false)
})
}
}
For apollo-server, install the apollo-server-errors package. For authentication errors,
import { AuthenticationError } from "apollo-server-errors";
Then, in your resolver
throw new AuthenticationError('unknown user');
This will return a 400 status code.
Read more about this topic in this blog
as you can see here formatError doesn't support status code, what you could do is create a status response type with message and status fields and return the corresponding on your resolver.
Does GraphQL require a 200 response even for failed queries / mutations?
No, if the query fails it will return null and the error that you throw in the server side.
Try adding response and setting the response status code as so, assuming your err.status is already an integer like 401 etc.:
const graphqlKoaMiddleware = graphqlKoa(ctx => {
return ({
schema,
response: request.resonse,
formatError: (err) => {
response.statusCode = err.status;
return ({message: err.message, status: err.status})},
context: {
stationConnector: new StationConnector(),
passengerTypeConnector: new PassengerTypeConnector(),
authConnector: new AuthConnector(),
cookies: ctx.cookies
}
})})
Based on Daniels answer i have managed to write middleware.
import { HttpQueryError, runHttpQuery } from 'apollo-server-core';
import { ApolloServer } from 'apollo-server-express';
// Source taken from: https://github.com/apollographql/apollo-server/blob/928f70906cb881e85caa2ae0e56d3dac61b20df0/packages/apollo-server-express/src/ApolloServer.ts
// Duplicated apollo-express middleware
export const badRequestToOKMiddleware = (apolloServer: ApolloServer) => {
return async (req, res, next) => {
runHttpQuery([req, res], {
method: req.method,
options: await apolloServer.createGraphQLServerOptions(req, res),
query: req.method === 'POST' ? req.body : req.query,
request: req,
}).then(
({ graphqlResponse, responseInit }) => {
if (responseInit.headers) {
for (const [name, value] of Object.entries(responseInit.headers)) {
res.setHeader(name, value);
}
}
res.statusCode = (responseInit as any).status || 200;
// Using `.send` is a best practice for Express, but we also just use
// `.end` for compatibility with `connect`.
if (typeof res.send === 'function') {
res.send(graphqlResponse);
} else {
res.end(graphqlResponse);
}
},
(error: HttpQueryError) => {
if ('HttpQueryError' !== error.name) {
return next(error);
}
if (error.headers) {
for (const [name, value] of Object.entries(error.headers)) {
res.setHeader(name, value);
}
}
res.statusCode = error.message.indexOf('UNAUTHENTICATED') !== -1 ? 200 : error.statusCode;
if (typeof res.send === 'function') {
// Using `.send` is a best practice for Express, but we also just use
// `.end` for compatibility with `connect`.
res.send(error.message);
} else {
res.end(error.message);
}
},
);
};
}
app.use(apolloServer.graphqlPath, badRequestToOKMiddleware(apolloServer));
apollo-server-express V3 supports this. Create your own plugin. Then you can look in the errors that are thrown to determine the status code.
import {ApolloServerPlugin} from "apollo-server-plugin-base/src/index";
const statusCodePlugin:ApolloServerPlugin = {
async requestDidStart(requestContext) {
return {
async willSendResponse(requestContext) {
const errors = (requestContext?.response?.errors || []) as any[];
for(let error of errors){
if(error?.code === 'unauthorized'){
requestContext.response.http.status = 401;
}
if(error?.code === 'access'){
requestContext.response.http.status = 403;
}
}
}
}
},
};
export default statusCodePlugin;