npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@brdu/express-authorizer

v0.6.6

Published

Express middleware handles JWT authentication and role-based authorization

Downloads

6

Readme

Introduction

This package handles authentication and role-based auhtorization for JSON Web Tokens (JWTs) as middlewares on Express Servers.

Usage

Get it up and running on your server with a single configuration file, in three easy steps:

  1. Add your Authentication parameters
  2. Add your Authorization parameters
  3. Create your policies
// file: gatekeeper.js, your configuration file
const { default: ExpressAuthorizer } = require('@brdu/express-authorizer')

const gatekeeper = new ExpressAuthorizer('EXPRESS')

gatekeeper.set_authentication_params(/* Authentication parameters */)

gatekeeper.set_authorization_params(/* Authorization parameters */)

gatekeeper.add_policy(/* Policy 1 */)
gatekeeper.add_policy(/* Policy 2 */)
/* ... */
gatekeeper.add_policy(/* Policy n */)

module.exports = gatekeeper

Import the configuration on your server file and apply the middlewares.

// file: server.js
const express = require('express')
const gatekeeper = require('./gatekeeper')

const app = express()
app.use(gatekeeper.authenticate) // <==
app.use(gatekeeper.authorize) // <==

/* Declare your routes */
app.use('*', (req, res) => res.sendStatus(404))

const port = process.env.PORT || 8080
app.listen(port, () => console.info(`server listening on port ${port}`))

The ExpressAuthorizer constructor takes a single parameter to initialize a new instance.

It accepts either one of EXPRESS or API_GATEWAY.

When initialized with the value EXPRESS, your server you'll be responsible for authenticating each request.

You're required to set up athentication parameters on your configuration if you intend to use the ExpressAuthorizer.authenticate middleware on your server.

Initializing the ExpressAuthorizer instance with the value API_GATEWAY means the request has been previously authenticated by an API Gateway or another service.

Authentication parameters will be ignored if declared, and using ExpressAuthorizer.authenticate middleware on your server will throw an exception.

You may choose to use only ExpressAuthorizer.authenticate if authorization isn't required, as well as only use ExpressAuthorizer.authorize if requests have been previously authenticated before received by your server.

Setting up Authentication Parameters

This method sets up all parameters required by the ExpressAuthorizer.authenticate middleware.

gatekeeper.set_authentication_params({
  secret_type: 'PEM',
  secret: 'https://www.googleapis.com/robot/v1/metadata/x509/[email protected]',
  secret_refresh_interval: 5,
  audience: 'my-app',
  issuer: 'https://securetoken.google.com/my-app',
})

| AuthenticationParams properties | | :--- | | secret_type (required, enum PLAIN_TEXT, PEM, JWK): The property specifies the type of secret available to verify the JWT sent with the request. | | secret (required): The property accepts multiple formats. (next) | | secret_refresh_interval (optional): Whenever your identity service uses key rotation for signing JWTs, you may specify the time interval (in minutes) those keys must be refreshed and ExpressAuthorizer will handle it in the background. | | audience (optional): When not declared, ExpressAuthorizer won't verify the aud claim from the request JWT. | | issuer (optional): When not declared, ExpressAuthorizer won't verify the iss claim from the request JWT. |

| secret accepted formats | | :--- | | String (format: url): When a url is passed as secret, ExpressAuthorizer will retrieve it when your server starts and pass the returned value to the JWT verifier. URLs are accepted for any secret_type value. | | String (format: any): This secret format is only accepted when service_type=PLAIN_TEXT. | | PEM Public Key(s): This secret format is only accepted when service_type=PEM. ExpressAuthorizer expects either a single PEM as a string or a JSON object with multiple PEMs where keys are each PEM kid. (example) | | JWK or JWKS: This secret format is only accepted when service_type=JWK. ExpressAuthorizer will retrieve the kid from the JWT header and build a PEM from your JWK. (example) |

Setting up Authorization Parameters

At the moment it's required all of the custom claims assigned to users to be nested under a root property. In the following example users_claims_root_key is organizations.

Users custom claims might be represented as a JSON object or as a collection (an array of objects).

Additionally, all claims in the organization object (organization_group, organization_id, roles) are required in your JWT. On the next minor version this requirement will be dropped.

{
  "organizations": [{
    "organization_group": "internal",
    "organization_id": "marketing",
    "roles": ["manager", "user"],
  }, {
    "organization_group": "internal",
    "organization_id": "global",
    "roles": ["user"],
  }],
  "iss": "https://securetoken.google.com/my-app",
  "aud": "my-app",
  "auth_time": 1620618241,
  "user_id": "Z0KuS5Hjn0UfBzW86p5zqGqBTIP2",
  "sub": "Z0KuS5Hjn0UfBzW86p5zqGqBTIP2",
  "iat": 1620618241,
  "exp": 1620621841,
  "email": "[email protected]",
  "email_verified": true,
  "firebase": {
    "identities": {
      "email": ["[email protected]"]
    },
    "sign_in_provider": "password"
  }
}

This method sets up all parameters required to map users' custom claims on JWTs. ExpressAuthorizer.authorize will work with the payload from the decoded JWT and your policies to determine users authority over any operation.

gatekeeper.set_authorization_params({
  user_id_key: 'user_id',
  user_claims_root_key: 'organizations',
  organization_group_key: 'organization_group',
  organization_id_key: 'organization_id',
  user_roles_key: 'roles',
  // The next properties apply only when auth_agent=API_GATEWAY
  identity_context_header_key: 'X-Endpoint-API-UserInfo',
  identity_context_transformation_function: (value) => {
    let parsedContext
    try {
      const contextString = Buffer.from(value, 'base64').toString('utf-8')
      parsedContext = JSON.parse(contextString)
    } catch (err) {
      parsedContext = {}
    }
    return parsedContext
  },
})

| AuthorizationParams properties | | :--- | | user_id_key (optional, default 'user_id'): It maps the user id key in your JWT. | | user_claims_root_key (optional, default 'organizations'): It maps the root key for your custom claims. Removing the requirement for a root to the custom claims object (or array) is in the project backlog. | | organization_group_key (optional, default 'organization_group'): It maps the key for organization groups in your JWT. It's currently required both in your authorization parameters, as well as in your policies. | | organization_id_key (optional, default 'organization_id'): It maps the organization id key in your JWT. | | user_roles_key (optional, default 'user_id'): It maps the key to the user roles array in your JWT. | | identity_context_header_key (optional, type String): In case your requests are authenticated by an API Gateway, when it's upstreamed to the internal service, the decoded JWT is usually passed on another header. | | identity_context_transformation_function (optional, type Function): Since request headers accept only strings - JSON objects can't be passed in the headers -, the function specified here will be responsible for hydrating the identity context back into a JSON object. |

Setting up Policies

Policies determine which users are allowed to consume your service operations. ExpressAuthorizer will extract relevant data from the JWT token, build the user roles and compare them against your policies to determine whether to deny acess (and return status code 403), or allow the request to reach your controllers.

ExpressAuthorizer assumes that routes not present in any policy are open to any authenticated request.

gatekeeper.add_policy({
  operations: [{
    path: '/users/:id',
    methods: ['POST', 'PATCH'],
  }],
  authorized_roles: 'self',
  user_id_alt_key: 'id',
})

gatekeeper.add_policy({
  operations: [{
    path: '/companies/:company_id/banking-info',
    methods: ['GET', 'POST', 'PATCH'],
  }],
  authorized_roles: [{
    organization_group: 'customers',
    roles: ['admin', 'billing'],
  }],
  organization_id_alt_key: 'company_id',
  organization_restricted: true,
})

Policy properties

| Policy properties | | :--- | | operations (required): An API operation is the combination of a path and a method. You may specify mutiple operations that must adhere to the same policy, and declare multiple methods for a single path - as seen on the previous example. | | operations.path (required): The endpoint path in Express format. | | operations.methods (required): The methods included in the policy for each one of the paths declared. | | authorized_roles (required): This is a complex property that accepts as value either a string or an array of objects. The accepted string values are 'self' and '*'; where 'self' refers to only the authenticated user itself, and '*' refers to any authenticated user. | | authorized_roles.organization_group (required): It refers to the organization groups allowed to access those operations; e.g. on an accounting system, organization groups could be represented by 'customers', 'suppliers', and 'internal_staff'. It also accepts '*' as a wildcard representing any organization group. | | authorized_roles.roles (required): It represents the authenticated user role(s) within an organization. This property accepts either an array of strings, as well as the wilcard '*' representing any role within an organization. | | user_id_alt_key (optional): When the declared authorized_roles is 'self', ExpressAuthorizer will look for a user_id in the path, then the request query string, and lastly in the request body. user_id_alt_key allows you to specify a key different from the one declared on AuthorizationParams.user_id_key | | organization_id_alt_key (optional): When the declared authorized_roles is an array of objects, ExpressAuthorizer will look for an organization_id in the path, then the request query string, and lastly in the request body. organization_id_alt_key allows you to specify a key different from the one declared on AuthorizationParams.organization_id_key | | organization_restricted (optional): The property tells ExpressAuthorizer whether or not to restrict resquests based on the organization_ids found on the JWT and the one found on the http request - either on its path, query string or the request body. |