GithubHelp home page GithubHelp logo

michieldemey / express-jwt-permissions Goto Github PK

View Code? Open in Web Editor NEW
518.0 11.0 37.0 509 KB

:vertical_traffic_light: Express middleware for JWT permissions

License: MIT License

JavaScript 100.00%
express jwt permissions middleware

express-jwt-permissions's Introduction

Express JWT Permissions

Node.js CI CodeQL codecov npm

js-standard-style

Middleware that checks JWT tokens for permissions, recommended to be used in conjunction with express-jwt.

Install

npm install express-jwt-permissions --save

Usage

This middleware assumes you already have a JWT authentication middleware such as express-jwt.

The middleware will check a decoded JWT token to see if a token has permissions to make a certain request.

Permissions should be described as an array of strings inside the JWT token, or as a space-delimited OAuth 2.0 Access Token Scope string.

"permissions": [
  "status",
  "user:read",
  "user:write"
]
"scope": "status user:read user:write"

If your JWT structure looks different you should map or reduce the results to produce a simple Array or String of permissions.

Using permission Array

To verify a permission for all routes using an array:

var guard = require('express-jwt-permissions')()

app.use(guard.check('admin'))

If you require different permissions per route, you can set the middleware per route.

var guard = require('express-jwt-permissions')()

app.get('/status', guard.check('status'), function(req, res) { ... })
app.get('/user', guard.check(['user:read']), function(req, res) { ... })

Logical combinations of required permissions can be made using nested arrays.

Single string

// Required: "admin"
app.use(guard.check(
  'admin'
))

Array of strings

// Required: "read" AND "write"
app.use(guard.check(
  ['read', 'write']
))

Array of arrays of strings

// Required: "read" OR "write"
app.use(guard.check([
  ['read'],
  ['write']
]))

// Required: "admin" OR ("read" AND "write")
app.use(guard.check([
  ['admin'],
  ['read', 'write']
]))

Configuration

To set where the module can find the user property (default req.user) you can set the requestProperty option.

To set where the module can find the permissions property inside the requestProperty object (default permissions), set the permissionsProperty option.

Example:

Consider you've set your permissions as scope on req.identity, your JWT structure looks like:

"scope": "user:read user:write"

You can pass the configuration into the module:

var guard = require('express-jwt-permissions')({
  requestProperty: 'identity',
  permissionsProperty: 'scope'
})

app.use(guard.check('user:read'))

Error handling

The default behavior is to throw an error when the token is invalid, so you can add your custom logic to manage unauthorized access as follows:

app.use(guard.check('admin'))

app.use(function (err, req, res, next) {
  if (err.code === 'permission_denied') {
    res.status(403).send('Forbidden');
  }
});

Note that your error handling middleware should be defined after the jwt-permissions middleware.

Excluding paths

This library has integration with express-unless to allow excluding paths, please refer to their usage.

const checkForPermissions = guard
  .check(['admin'])
  .unless({ path: '/not-secret' })

app.use(checkForPermissions)

Tests

$ npm install
$ npm test

License

This project is licensed under the MIT license. See the LICENSE file for more info.

express-jwt-permissions's People

Contributors

alexzeitler avatar andrewjo avatar bveenema avatar dependabot[bot] avatar eugenio4 avatar gillesdemey avatar gugu avatar kahuna-celsius avatar michieldemey avatar mike-engel avatar sebdeckers avatar yacovgold avatar zorji avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

express-jwt-permissions's Issues

unauthorized access returns an exception trace

unauthorized access returns an exception trace

were/how do I change that to returning an HTML error 403 ?

UnauthorizedError: Permission denied
at Object. (/path/to/my/dev/folder/node_modules/express-jwt-permissions/index.js:4:23)
at Module._compile (module.js:556:32)
at Object.Module._extensions..js (module.js:565:10)
at Module.load (module.js:473:32)
at tryModuleLoad (module.js:432:12)
at Function.Module._load (module.js:424:3)
at Function._load (/usr/local/lib/node_modules/pm2/node_modules/pmx/lib/transaction.js:62:21)
at Module.require (module.js:483:17)
at require (internal/module.js:20:19)
at Object. (/path/to/my/dev/folder/index.js:11:49)

'ErrorCode' and '"permission_denied"' have no overlap

Hey there, thanks for jwt permissions!

This condition will always return 'false' since the types 'ErrorCode' and '"permission_denied"' have no overlap.ts(2367)

if (err instanceof UnauthorizedError) 
          if ((err as UnauthorizedError).code === "permission_denied")
    export type ErrorCode =
        "revoked_token" |
        "invalid_token" |
        "credentials_bad_scheme" |
        "credentials_bad_format" |
        "credentials_required"
    export class UnauthorizedError extends Error  {
        status: number;
        message: string;
        name: 'UnauthorizedError';
        code: ErrorCode;
        inner: { message: string };

        constructor(code: ErrorCode, error: { message: string });
    }

API route permissions using an express-jwt-permissions guard on node.js

Originally posted on Stack Overflow

I am attempting to use express-jwt-permissions to protect an API route and I am unable to use the guard syntax "guard.check('user')". I have successfully used express-jwt which express-jwt-permissions builds upon.

An interesting fact is that express-jwt requires the JWT_SECRET to be assigned to it, whereas there are no instruction on the express-jwt-permissions docs to understand this interplay or perhaps there is some example missing?

My current code is as follows:

/////////////////////////////////////////////
// auth.ts - Auth and set user permissions
/////////////////////////////////////////////
router.post('/', async (request, response, next) => {
    const {email, password} = request.body

    try {
        // Authenticate
        const user = await authenticate(email, password)
        user.permissions = ['user'] // set the express-jwt-permissions here

        // Create JWT token
        let token = jwt.sign(user.toJSON(), process.env.JWT_SECRET, {
            expiresIn: '60m'
        })

        let {iat, exp} = jwtDecode(token)

        // Respond with token
        response.status(HttpStatus.OK).send({iat, exp, token})
    

...

I successfully retrieved a JWT token from the 'api/auth' endpoint.

{
    "iat": 1559650778,
    "exp": 1559654378,
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwZXJtaXNzaW9ucyI6WyJ1c2VyIl0sIl9pZCI6IjVjZjUyZjc1NDA4MTk0YWI1MGZlMWNkNiIsIm5hbWUiOiJHYXJ5IFBhbHVrIiwiZW1haWwiOiJnYXJ5QHBsdWdpbi5pbyIsInVzZXJuYW1lIjoiZ2FyeSIsInBhc3N3b3JkIjoiJDJhJDEwJEt1U1NUQXowd1MxNU5tRjRVQjZQb2VMTC5Ya1phZkc5Sm9xVkVRWnZZcHFkTFNrZXliTU1lIiwidXBkYXRlZEF0IjoiMjAxOS0wNi0wM1QxNDozMjoyMS4zMDlaIiwiY3JlYXRlZEF0IjoiMjAxOS0wNi0wM1QxNDozMjoyMS4zMDlaIiwiX192IjowLCJpYXQiOjE1NTk2NTA3NzgsImV4cCI6MTU1OTY1NDM3OH0.qnfH_OHq2YqaKCRIbwtw788SQC51F8PJESRCf3Nlrak"
}

I then attempted to authorize with combinations of Bearer Token, OAuth2, prepending token with/without 'jwt ' etc, but nothing seems to get past the route guard on 'api/registry'.

/////////////////////////////////////////////
// server.ts - API auth routes
/////////////////////////////////////////////
server.use(
    '/api/registry',
    guard.check('user'),
    require('./api/v1/registry')
)

server.use('/api/auth', require('./api/v1/auth'))


...

Result:

{
    "name": "UnauthorizedError",
    "message": "user object \"user\" was not found. Check your configuration.",
    "code": "user_object_not_found",
    "status": 403,
    "inner": {
        "message": "user object \"user\" was not found. Check your configuration."
    }
}

The expected result would be that I can make an API call to '/api/registry' with the JWT token as a bearer token / OAuth2? and that should let me pass the route guard.

Thanks

Should not call next when `requestProperty` is not found.

We shouldn't be calling next without an exception when requestProperty is not found.

var user = req[options.requestProperty]
if (!user) return next()

When express-jwt isn't there to catch unauthenticated users (since this middleware only deals with authorization and expects authentication to already have been performed) the route would be open to everyone.

In order to prevent such a scenario we should throw an exception stating that the requestProperty couldn't be found, either because it was incorrectly configured or because authentication has not been performed prior to using express-jwt-permissions.

UnauthorizedError: Could not find permissions for user. Bad configuration?

Hi, i'm using express-jwt-permissions with jwt-simple library.

My JWT is the next:

{
"sub": "5b0330a31606984fbf311c29",
"iat": 1527308253,
"exp": 1558844253,
"permissions": [
"user",
"user:create",
"user:read",
"user:all",
"user:update",
"user:delete"
]
}

Use guard.check('user') in my endpoint but i have an error

example:

api.get('/users', isAuth, guard.check('user'), UserCtrl.getUsers)

someone has a solution?

Cannot use if token is passed as Authorization header

Based on Express JWT:

A custom function for extracting the token from a request can be specified with the getToken option. This is useful if you need to pass the token through a query parameter or a cookie. You can throw an error in this function and it will be handled by express-jwt.

app.use(jwt({
secret: 'hello world !',
credentialsRequired: false,
getToken: function fromHeaderOrQuerystring (req) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
}));


How do I integrate permissions in this case?

user object "user" was not found. Check your configuration.

I dont get the error
user object "user" was not found. Check your configuration.

here my token:
{
"role": "ADMIN",
"name": "[email protected]",
"lastConnection": "2019-06-26T06:50:52.992Z",
"email": "[email protected]",
"scope": "admin articles",
"exp": 1561540712,
"iat": 1561533512
}

the token is supposed to be like that ?:
{
"role": "ADMIN",
"name": "[email protected]",
"lastConnection": "2019-06-26T06:50:52.992Z",
"email": "[email protected]",
"user": {
"scope": "admin articles",
}
"exp": 1561540712,
"iat": 1561533512
}

because it doesn't work...

Consider adding "native support"/integration for express-unless

express-unless that is being used by packages like express-jwt allows easily to exclude global middleware (applied using app.use()) from certain paths.

Having support for this would make it easy for users to check for permissions on all paths except certain ones. In this case things like this could be possible (without extra config):

//Check for write permission unless it's a GET request
app.use(guard.check("write").unless({method: "GET"}));

//Check for write permission unless it's the /auth endpoint
app.use(guard.check("write").unless({path: "/auth"}));

//Combined works of course aswell
app.use(guard.check("write").unless({method: "GET", path: "/auth"}));

Their docs have instructions about how to integrate it with packages.


It is of course possible to use express-unless without "native support"/integration, but its configuration is a bit confusing and needs a bit of "mixing" and a few more lines of code on the user's side.

Error on Typescript compilation

If I try to compile it with typescript I get the following error:

node_modules/express-jwt-permissions/index.d.ts:1:10 - error TS2305: Module '"express-unless"' has no exported member 'RequestHandler'. The Problem seems to occurs if some other module uses express-unless above 1.0.0. Maybe it would be a good idea to update the dependencies and the related typings?

Array of permissions

Hello,

How do i pass in an array of permissions to check?
Let's say the user has permissions: Admin, how can i make the guard to check if any of the users permissions match the ones in the guard?

router.get('/', guard.check(['Admin', 'SuperAdmin'])) does not work.

Permission denied when using PUT method

Hey! I'm getting an UnauthorizedError when using the PUT method. Works with POST, so that's what I'll be using for now, but I'm curious. I think it should work just the same with all allowed methods on res.header, right?

Am I missing something?

I need a per-route ignore

I need a guard.ignore() that I can put inline on specific routes

I am using express-jwt with credentialsRequired: trueto ensure that I only accept calls that include a JWT in them

But there are a few routes for which I do not want to use guard.check()

I need to have it both ways !!

:)

Bad configuration

Hi i have an issue always get this message,

"Could not find permissions for user. Bad configuration?"

Permission denied when no user object is set

When a user object is not set, the middleware throws a permissions error.
Not having a user object simply means that the route doesn't require any authentication/authorization.

Access missing permission in error object

At first thank you very much for this helpfull and handy module. It works very fine.

For me it would be very helpful if i can access the missing permission at the error handler:

Example:

app.use(guard.check('admin'))

app.use(function (err, req, res, next) {
  if (err.code === 'permission_denied') {
    const missingPermisson = err.missing;
    res.status(403).send('Forbidden - Missing permission: ' + missingPermission);
  }
});

If it would be possible to access the missing permission via the error object I'm able to respond it to the client.

Suggestion: Documentation enhancement

Thank you so much for this package. The simplicity and flexibility enabled is awesome.

I'd like to suggest an addition to documentation. The context being, many of us understand the concept of Auth, but unless we've been deep in the weeds, we don't find it intuitive. So at first glance, the below wouldn't occur to someone who was looking to check permissions, but then wanted to check roles, or some other attributes of the token:

Here we check permissions, under the auth attribute...

const Permissions = useGuard({
requestProperty: "auth",
permissionsProperty: "permissions"
});

Here we configure a guard to look in namespaced roles

const Roles = useGuard({
requestProperty: "auth",
permissionsProperty: ${Config.namespace}/roles
});

The point we miss as "novices" is that the package allows us to setup many different types of guards and apply them in parallel.

Thanks again!

[Question] Purpose of this library

Newbie question alert.

Why would you encode permissions in a JWT that gets sent to the user? It's true that we can detect tampering, but nonetheless, why expose this to the world?

Why not get the token, determine which user it is, then decide on which permissions to apply. A database read would be required anyway.

Is it possible to check multiple permissionsProperty

Auth0 released their Core authorization and if i understand correctly , when i chose to add permissions to access token, its in the "permissions" property.

If the API is called by client_credential application, the token will have scopes associated with this application instead of permissions.

Could express-jwt-permissions be used to check both "permissions" and "scopes" at the same time?

Cannot change `requestProperty` object.

Cannot seem to change the requestProperty of my user object to payload. Double checked req.payload and its working fine without the guard.

Here's my express-JWT config:

let auth = {
    required: jwt({
        secret: secret,
        userProperty: 'payload',
        getToken: getTokenFromHeader
    })
}

signing jwt

    return jwt.sign({
      id: this._id,
      username: this.username,
      exp: parseInt(exp.getTime() / 1000),
      permissions: ['r','w']
    }, secret);

Set using let guard = require('express-jwt-permissions')({requestProperty: 'payload'});
Accessing it using guard.check('r') throws 'user object payload was not found. Check your configuration.'

Version 1.0.0

We should consider releasing a 1.0.0 version, it inspires more confidence in what is already a stable module and some NPM search engines will otherwise mark it as unstable.

/cc @MichielDeMey

how to handle this use case

Is there a way to configure such that permission should be granted if either "user:read" or "user:write" strings are present in the permissions object array of strings in the token payload?

Node Testing How to stub middleware using express-jwt-permissions guard

Hi,
I am using express-jwt-permissions and supertest but I want two things:

I want to stub the middleware that I am using.
I want to pass the guard restricction:
I'll go to share a piece of code:

...
MyRoles: guard.check([
    ['admin'],
    ['user']
  ])
...
app.post("/myendpoint", MyRoles, async (req, res) => {
  console.log(1)
}

I don't arrive to the console.log but, if I remove the middleware MyRoles everything works well.

This is my unit test:

it("should respond with a 200", function (done) {
    request
      .post("/myendpoint")
      .set("Authorization", "Bearer eyJhbGciOi.eyJkYXRhIjp7InVzZ")
      .expect("Content-Type", /json/)
      .expect(200)
      .end(function (err, res) {
        ...
        done()
      });
});

In the User interface everything works perfectly but I need the two requirements (stub the middleware and pass the middleware by token)

I am hardcoding the token but it didn't pass the middleware, always return 403.

If you have any ideas, please let me know.

Thank you very much.

Use native Object.assign instead of xtend

xtend can easily be replaced with Object.assign

This reduce the dependencies and possible also avoid duplicated versions where some package don't use the same version range for the xtend package

// immutable
Object.assign({}, a, b)

// mutable
Object.assign(a, b)

Add option to disable logs

While running my tests, my stdout is getting cluttered with UnauthorizedError: Permission denied error logs.
I really don't want to mock console.error calls, and I couldn't find any entry point for disabling logging while testing.

I don't think the default behaviour of the tool should even call console.error, tho.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.