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

hono_jwt_auth

v1.2.10

Published

JWT based authentication and session middleware for Hono

Downloads

328

Readme

Hono JWT Auth Middleware

Description

This is middleware for Hono that combines JWT with cookies to form an authentication and session manager.

This middleware supports Node.JS. This should theoretically work with Cloudflare Workers (with the nodejs_compat compatibility flag), Cloudflare Pages, Deno, Bun and any others that have Node.JS compatibility for node:crypto (only randomUUID) and node:buffer (only Buffer). PRs are always welcome.

Installation

npm i hono_jwt_auth

Usage

import { Hono, type Context } from 'hono';
import { env } from 'hono/adapter';
import { JWTAuth, type CookieConfig, type JWTConfig, type JWTAuthConfig } from 'hono_jwt_auth';

type Bindings = {
    SIGNED_COOKIE_SECRET: string
    JWT_SECRET: string
}

type UserData = {
    displayName: string
    memberOf: string[]
}

const app = new Hono<{ Bindings: Bindings }>(),
    authCookieConfig: CookieConfig = {
        key: 'Authorization',
        httpOnly: true,
        secure: true,
        prefix: 'host',
        path: '/',
        sameSite: 'Strict',
        secret: (c: Context) => env(c).SIGNED_COOKIE_SECRET,
    },
    authJWTConfig: JWTConfig = {
        nbf: 300,
        alg: 'HS512',
        secret: (c: Context) => env(c).JWT_SECRET,
    },
    jwtAuthConfig: JWTAuthConfig = {
        domain: 'www.example.com',
        sessionLength: 43200,
        redirectURI: '/login',
        redirectStyle: 'referrer',
        cookie: authCookieConfig,
        jwt: authJWTConfig,
        logging: true,
        logger: console.log
    }
    sessionManager = new JWTAuth<UserData>(jwtAuthConfig);

app.get('/login', sessionManager.loginPageHelper(), c => {
    // HTML form to receive credentials, form POSTs to /api/login
    return c.html('<form>...</form>');
});

app.get('/logout', async c => {
    await sessionManager.removeSession(c);
    c.redirect('/login');
});

app.post('/api/login', async c => {
    const creds = await c.req.parseBody<{ user: string, pass: string }>();
    let user = creds['user'],
        pass = creds['pass'];
    // check credentials against separate user database, then proceed:
    if (authenticated) {
        await sessionManager.addSession(c, user, userData); // create a new session for this user
        return c.text('Authenticated', 200);
    } else {
        return c.text('Unauthenticated', 401);
    }
    // client side should check the statusCode to know how to proceed.
});

/* Example Protected Routes */

app.get('/protected',
    sessionManager.pageAuth((user, userData) => {
        // Add a check if user can access this page and return true or false accordingly.
        // The data passed to addSession is accessible here.
        // If true, user will proceed to the page,
        // If false, user will be redirected to the redirectURI
        // This can be used for any route that should redirect when the user is forbidden
    }),
    c => {
        return c.html('<p>Protected Page</p>');
    }
);

app.patch('/api/userInfo',
    sessionManager.apiAuth((user, userData) => {
        // Add a check if user can access this endpoint and return true or false accordingly.
        // The data passed to addSession is accessible here.
        // If true, the request will proceed to the endpoint,
        // If false, the request will be returned with HTTP 403 Forbidden
        // This can be used for any route that should receive a 403 status code when the user is forbidden
    }),
    c => {
        /* ... */
    }
);

You can also use await sessionManager.getSession(c) to get the current user's stored data, while also checking that the user's session is still valid.

Documentation

JWTAuth is the primary object. An object with type JWTAuthConfig can be passed in the constructor of JWTAuth to configure it.

import { JWTAuth } from 'hono-jwt-auth'

const sessionManager = new JWTAuth();

Methods

An instance of JWTAuth has the following methods.

  • sessionManager.addSession(c, subject, data)
  • sessionManager.getSession(c)
  • sessionManager.removeSession(c)
  • sessionManager.loginPageHelper()
  • sessionManager.pageAuth(accessCheckHandler)
  • sessionManager.apiAuth(accessCheckHandler)

addSession()

Create a session for a user that you independantly authenticate. This should be used in a route as part of the login process. Once the user has successfully been authenticated, use this method to add a new session to the session manager. Be sure to use the await keyword on this method before returning a response (or return this method and use the .then method to chain a response).

Params:

  • c: Context from 'hono'
  • subject: string, can be the username of the user, used in JWT as the value for the sub key.
  • data: UserData

Returns: Promise<void>

Both subject and data are used in the pageAuth and apiAuth middleware methods to assist in checking if a user has permision to access a resource.

getSession()

Retrieves the subject and data of the current user as set by addSession. Can be used in a route handler to display user specific information.

Params:

  • c: Context from 'hono'

Returns: Promise<[string, UserData]>

removeSession()

Removes the current user's session and deletes the session cookie. This should be used in a route as part of the logout process. Use this method to invalidate their session. Be sure to use the await keyword on this method before returning a response (or return this method and use the .then method to chain a response).

Params:

  • c: Context from 'hono'

Returns: Promise<void>

loginPageHelper()

Use this method as middleware on the login page route. This checks if the user is logged in or not. If logged in, the user is either sent back to the page they came from or sent to the root index page. If not logged in, the user is served the login page.

How this redirects is determined by the configuration option redirectStyle. If set to 'referrer' (default), then this handler checks the referrer property of the Request and uses that as the redirect location. Otherwise, if set to 'query', redirects to the login page will include the page in Base64 encoding in the URL as the r query. For example, if page '/locked' redirects to the login page, it redirects to '/login?r=L2xvY2tlZA=='. If the user is still logged in, they will be sent back to '/locked' based off this URL query.

If the referrer or query is the login page, to help prevent a redirect loop, it redirects to the root index page.

Returns: MiddelwareHandler from 'hono'

Example:

app.get('/login', sessionManager.loginPageHelper(), c => {
    // HTML form to receive credentials, form should POST to /api/login
    return c.html('<form>...</form>');
});

pageAuth()

Use this method as middleware to ensure a user-facing route is protected. The session manager will allow an authenticated user to access the resource if they pass a developer-defined check in the (optional, but recommended) access check handler.

Unauthenticated users will be redirected to the route defined in the manager's redirectURI option. An authenticated user that fails the access check is considered unauthorized and is forbidden from accessing the resource. An HTTPException is then thrown with HTTP code 403.

Params:

  • accessCheckHandler: (subject: string, userData: UserData) => boolean

Returns: MiddelwareHandler from 'hono'

apiAuth()

Use this method as middleware to ensure an API route or other resource (like a static file) is protected. The session manager will allow an authenticated user to access the resource if they pass a developer-defined check in the (optional, but recommended) access check handler.

A request made by an unauthenticated users will be returned with a text response with the WWW-Authenticate header set to indicate the lack of authentication. A request made by an authenticated user that fails the access check (considered unauthorized and is forbidden from accessing the resource) throws an HTTPException with HTTP code 403.

Params:

  • accessCheckHandler: (subject: string, userData: UserData) => boolean

Returns: MiddelwareHandler from 'hono'

Generics

You can pass Generics to specify the types of data used in the above methods.

type UserData = {
    displayName: string
    memberOf: string[]
}

const sessionManager = new JWTAuth<UserData>();

app.post('/api/login', async c => {
    let userData: UserData;
    // ... check credentials, assign needed user information from DB to userData
    await sessionManager.addSession(c, username, userData)
    // ...
})

app.use('/auth/*',
    sessionManager.pageAuth((subject, userData) => {
        if (userData.memberOf.includes('someGroup')) {
            return true // allow client to access resource
        } else {
            return false // forbid client from accessing resource
        }
    }),
    c => c.text('User is allowed to access this')
)

Types

JWTAuthConfig

type JWTAuthConfig = {
    domain?: string,
    sessionLength?: number,
    redirectURI?: string,
    redirectStyle?: 'referrer' | 'query'
    cookie?: CookieConfig,
    jwt?: JWTConfig,
    logging?: boolean,
    logger?: (str: string, ...rest: string[]) => void
}

// ...
import type { JWTAuthConfig } from 'hono-jwt-auth';
  • domain - site domain, ensures the cookie is only used this domain
    • type: string
    • default: 'localhost'
  • sessionLength - number of seconds until the session expires
    • type: number
    • default: 43200 (12 hours)
  • redirectURI
    • type: string - login page
    • default: /login
  • redirectStyle
    • type: 'referrer' | 'query' - login page redirect style, see the loginPageHelper() method for more information
    • default: referrer
  • cookie
  • jwt
  • type: JWTConfig
  • default: see defaults for type JWTConfig
  • logging - can be set to true to see logs of JWTAuth
    • type: boolean
    • default: false
  • logger - can be set to a logging function, takes the same value as Hono's logger middleware
    • type: (str: string, ...rest: string[]) => void
    • default: console.log

CookieConfig

type CookieConfig = {
    key?: string,
    secret?: string | ((c: Context) => string),
    httpOnly?: boolean,
    secure?: boolean,
    prefix?: CookiePrefixOptions,
    path?: string,
    sameSite?: 'Strict' | 'Lax' | 'None',
    partition?: boolean
}

// ...
import type { CookieConfig } from 'hono-jwt-auth';
  • key - cookie name
    • type: string
    • default: 'Authorization'
  • secret - can be string or function with the Hono Context as a parameter, used to sign the cookie
    • type: string | ((c: Context) => string)
    • default: crypto.randomUUID()
  • httpOnly - required to be true if the prefix is 'host', can be false if the prefix is 'secure' or isn't defined
    • type: boolean
    • default: true
  • secure - required to be true if prefix is 'host' or 'secure', can be false only if prefix isn't defined
    • type: boolean
    • default: true
  • prefix - cookie prefix, can be 'host' for _HOST or 'secure' for _Secure
    • type: CookiePrefixOptions from 'hono/utils/cookie'
    • default: none
  • path - root path this cookie is valid on
    • type: string
    • default: '/'
  • sameSite - can be 'None', 'Lax', or 'Strict'
    • type: 'Strict' | 'Lax' | 'None'
    • default: 'Strict'
  • partitioned - opt-in to using CHIPS
    • type: boolean
    • default: true

JWTConfig

type JWTConfig = {
    nbf?: number,
    secret?: string | ((c: Context) => string),
    alg?: "HS512" | "HS256" | "HS384"
}

// ...
import type { JWTConfig } from 'hono-jwt-auth';
  • nbf - number of seconds before issuance that the token is valid
    • type: number
    • default: 300 (5 minutes)
  • secret - can be string or function with the Hono Context as a parameter, used to sign the token
    • type: string | ((c: Context) => string)
    • default: crypto.randomUUID()
  • alg - JWT algorithm, depends on Hono's support
    • type: "HS512" | "HS256" | "HS384"
    • default: "HS512"

License

This project is license under MIT.