hono_jwt_auth
v1.2.10
Published
JWT based authentication and session middleware for Hono
Downloads
328
Maintainers
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 thesub
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'
- type:
sessionLength
- number of seconds until the session expires- type:
number
- default:
43200
(12 hours)
- type:
redirectURI
- type:
string
- login page - default:
/login
- type:
redirectStyle
- type:
'referrer' | 'query'
- login page redirect style, see the loginPageHelper() method for more information - default:
referrer
- type:
cookie
- type:
CookieConfig
- default: see defaults for type
CookieConfig
- type:
jwt
- type:
JWTConfig
- default: see defaults for type
JWTConfig
logging
- can be set to true to see logs of JWTAuth- type:
boolean
- default:
false
- type:
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
- type:
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'
- type:
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()
- type:
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
- type:
secure
- required to be true if prefix is 'host' or 'secure', can be false only if prefix isn't defined- type:
boolean
- default:
true
- type:
prefix
- cookie prefix, can be 'host' for _HOST or 'secure' for _Secure- type:
CookiePrefixOptions
from 'hono/utils/cookie' - default: none
- type:
path
- root path this cookie is valid on- type:
string
- default:
'/'
- type:
sameSite
- can be 'None', 'Lax', or 'Strict'- type:
'Strict' | 'Lax' | 'None'
- default:
'Strict'
- type:
partitioned
- opt-in to using CHIPS- type:
boolean
- default:
true
- type:
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)
- type:
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()
- type:
alg
- JWT algorithm, depends on Hono's support- type:
"HS512" | "HS256" | "HS384"
- default:
"HS512"
- type:
License
This project is license under MIT.