Nearest Phase Modulator

    jwtproxy

    1.6.9 • Public • Published
    Build and NPM package status
    Node.js Package NPMJS registry Node.js CI

    Description

    jwtproxy provides a simple middleware component with a few switches that allow verification of JWT using either symmetric / shared keys or RSA keys. Relying upon two other libraries - jsonwebtoken and jwks-rsa it further simplifies and extends the configration of the needs.

    Overview - jwtproxy

    Often there is a need for a simple JWT validation or verification within a Node.js, or Express.js application. While there are options such as reverse proxies like:

    The initial need was to simplify a needed deployment for basic JWT validation and not initially overcomplicate a Kubernetes deployment with added side-car containers, ingress containers, etc. While some Cloud providers have some of these capabilities ready to go, this component simplifies the initial need and allows evolution of the concern before biting off on something much more significant.

    It can also be enabled or disabled via a configuration or environment feature switch.

    The initial goals were:

    1. must be an Express middleware component that provides a simple way to include or excluded within the pipeline
    2. further simplifiy and externalize settings as needed leaning on dotenv and .env files for local, and environment variables for runtime
    3. while injected into the pipeline should be easy to disable, modify, etc.

    Note: more details are in Overview.

    Setup and installation

    There is an example ExpressJS application in the ./example folder. Written in TypeScript (both the component and sample Express Js App) it is fully transpiled to JavaScript.

    Adding to Express

    Like any middleware for ExpressJS, this should be inserted early on in the pipeline. Since it reads the raw NodeJS HTTP Headers it can be before other middleware that parses cookies, the body, etc.

    In the following, we allow two algorithms, and exclude two paths from checking if the JWT is in the Authorization header. And, while we do state RS256, we actually only use HS256 as that is the symmetric algoritm used. We are not setting up a Jwks URL for retreival of keys.

    Example setup

    import jwtProxy, { JwtProxyOptions } from 'jwtproxy'
    
    const sharedSecret = 'notagreatsecret'
    
    const options: JwtProxyOptions = {
      algorithms: [ 'RS256','HS256'],
      excluded: ['/status', '/sign'],
      secretOrKey: sharedSecret
    }
    
    const proxy = jwtProxy(options),
    this.app.use(proxy)

    Quick start with sample

    NOTE: Make sure you have NodeJs 10x or better and npm version 5.4 or greater.

    Navigate to ./example folder and execute the following:

    # the ci command uses the package-lock.json or shrinkwrap to do a deterministic install
    npm ci 
    npm run build
    npm start

    At this point you should see something similar:

    $ npm run fresh
    
    > example@1.0.0 fresh E:\g\npm-projs\jwtproxy\example
    > npx npm-run-all ci build start
    
    npx: installed 58 in 6.288s
    
    > example@1.0.0 ci E:\g\npm-projs\jwtproxy\example
    > npm ci
    
    npm WARN prepare removing existing node_modules/ before installation
    added 60 packages in 2.925s
    
    > example@1.0.0 build E:\g\npm-projs\jwtproxy\example
    > tsc --build .
    
    
    > example@1.0.0 start E:\g\npm-projs\jwtproxy\example
    > node server.js
    
    App listening on the http://localhost:5000
    
    
    

    one line startup

    The package.json has a simple npm script that combines all three -- just run the following as an alternative if you want to save a few keystrokes.

    npm run fresh
    

    Port in use

    If you see something like the following, that means the port is already used by some other process.

    > example@1.0.0 start E:\g\npm-projs\jwtproxy\example
    > node server.js
    
    events.js:183
          throw er; // Unhandled 'error' event
          ^
    
    Error: listen EADDRINUSE 127.0.0.1:5000
        at Server.setupListenHandle [as _listen2] (net.js:1360:14)   
        at listenInCluster (net.js:1401:12)
        at GetAddrInfoReqWrap.doListen [as callback] (net.js:1510:7) 
        at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:72:10)
    npm ERR! code ELIFECYCLE
    npm ERR! errno 1
    npm ERR! example@1.0.0 start: `node server.js`
    npm ERR! Exit status 1
    npm ERR! 
    npm ERR! Failed at the example@1.0.0 start script.
    npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
    

    To address this set an environment variable either in a .env file like the following, or with your operating system shell method -- export PORT=5500 or in PowerShell $env:PORT=5500. In bash/zsh you can also just pass this on the command line as PORT=5500 npm start

    PORT=5500
    HOST=localhost
    
    

    Either create or modifying existing Express JS application

    npm install jwptproxy

    npm i jwtproxy

    modify your express app

    Follow the instructions above in [Example setup](Example setup) for the modifications.

    Sequence Diagram Jwt Proxy

    
    sequenceDiagram
        participant User
        participant App
        participant Module
        participant KeyHelper
        participant JwksHost
        App->>+Module: Options?
        Note left of Module: optional and if present <br/> than NO env variables are used. 
        User->>+App: api(h:jwt)
        App->>+Module: verify(jwt)
        Module->>Module: decode(kid,claims)
        Note Right of Module: decode but not verify <br/>
        Module->>+KeyHelper: getKey(decoded token, kid)
        alt Local or Remote
          KeyHelper->>+JwksHost: getKeys()
        else
          KeyHelper->>KeyHelper: getKey(kid, filePath)
          Note right of KeyHelper: developer and test experience <br/> without relying on <br/>external Jwks Host [A]
        end
        KeyHelper-->>-Module: key for kid
        Module-->>-App: done
        App-->>-User: ok/not ok
    
    

    Options object

    An option object is optionaly injected at the start. The presence of the options object implies that NO environment variables are to be used. This permits couple of scenarios

    • Explicit paths that need something different - so the injection can override what an env variable might provide. Regardless, even in that situation, the code that creates the options object can also pull from env.
    • Developer and Test scenarios - Test frameworks need to create valid tokens, and the verification needs some key data offline and disconnected at least during unit testing. For develper experience, this allows ease of use whithout the need to standup a signing authority and Jwks Endpoint just to try out the module.
    /** Options for THIS middlware */
    export interface JwtProxyOptions {
      disable?: boolean|undefined,
      secretOrKey?: string,
      audience?: string,
      issuer?: string,
      jwksUrl?: string,
      algorithms?: Algorithm[],
      excluded?: string[]
    }
    option purpose example
    disable completely disable the middleware and do NO checks disable = true
    secretOrKey either the shared key or if jwksUrl is populated, this can be blank secretOrKey='notagreatkey'
    audience a list of audiences in one line separated by ; - if ANY of the aud match, then the verify passes audience = 'shipping;receiving;marketing'
    issuer for jwks RSA verification of the issuer on the JWT issuer = 'foo.bar.com'
    jwksUrl the HTTP endpoint used for retrieval of keysets then used to match the key name present in the JWT see jwks jwksUrl = 'https://YOUR_DOMAIN/.well-known/jwks.json'
    algorithms any of the supported algorithms within jsonwebtoken see Algorithms algorithms = ['RS256', 'HS258']
    exluded array of paths that are specifically exluded by exact match on Request.URL not including the query string exluded= ['/status', '/health', '/issue']

    Disable check

    The following uses env or options and if EITHER are set to disable, the middleware is turned OFF.

      const envDisabled: boolean = process.env.JWTP_DISABLE == null ? false : process.env.JWTP_DISABLE.toLowerCase() === 'true';
      const optDisabled = proxyOptions?.disable as boolean;
      if (optDisabled || envDisabled)
      {
        logger('jwt proxy disabled - no jwt verification will occure'); 
        return async (req:Request, res:Response, next:NextFunction): Promise<void> =>{
          next();
          return;
        }
      }
    

    Rule for Options

      secretOrKey?: string,
      audience?: string, - 'if present also validate'
      issuer?: string, - 'if present also validate'
      jwksUrl?: string,
      algorithms?: Algorithm[], - 'if there is an alt alg to use for token verification - note that HS256 is default'
      excluded?: string[] - 'paths to exlude in routes'
    { exclude?: ,
      alg?: ,
      jwksurl: 'this can be `string|function`,
      iss? : ,
      aud? : }

    alg

    if algorithms is supplied, this becomes a verification aspect. Jwt tokens supply the algorithms as part of the header, along with the kid. We use the algorithms supplied to as the constrained set - if the alg on JWT token is not in the set it fails validation.

    jwksurl

    if the jwksurl is a string, it is parsed to ensure its valid url. If so, then the JwksHost is to be called for the keyset and key for kid.

    if jwksurl is a string and not a url, it is assumed to be the verification key - in RS256 this is the public key material in PEM format.

    If the jwksurl is a function then it implements a known interface (TBD) that provides a callback that delivers the secret or key for the kid

    Notes: from the diagram

    • [A] Key information provided as part of the options object or an environment variable can be full URL or file reference to local PEM file that contains the public key to verify the singature.

    Environment Variables.

    The presence of an options object precludes any env variables to be used for processing. If the options object is absent or empty, the env variables are used for configuration.

    Note: all of the jwtproxy variables are prefixed with JWTP_

    • JWTP_ALG: string
    • JWTP_URL: string
    • JWTP_ISS: string
    • JWTP_AUD: string = 'myaudience'
    • JWTP_EXCLUDE: string = /path1,path2/ --

    It is NOT expected in the env use case that the JWTP_URL will be used as a function type. Either a pure PEM public key for signature verification or a proper URL to a Jwks Host endpoint that provide key material.

    Install

    npm i jwtproxy

    DownloadsWeekly Downloads

    254

    Version

    1.6.9

    License

    Apache-2.0

    Unpacked Size

    340 kB

    Total Files

    41

    Last publish

    Collaborators

    • cicorias