-
-
Notifications
You must be signed in to change notification settings - Fork 662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(hono/jwk): JWK Auth Middleware #3826
Conversation
…yAlg fallback, add decodeHeaders utility - Added "kid" (Key ID) for TokenHeader - Fixed Jwt.sign() ignoring privateKey.alg - Renamed `alg` parameter to `KEYAlg` to differentiate between privateKey.alg - Added utility function `decodeHeaders` to decode only JWT headers
Hono JWK Middleware main features: - Ability to provide a list of public JWKs to the keys parameter as a simple javascript array [] - Ability to provide a URL in the jwks_uri parameter to fetch keys from + an optional RequestInit (useful for caching if your cloud provider has a modified fetch that supports it, or if you simply want to modify the request) - Ability to provide an async function that returns an array to the keys parameter instead of a direct array, so that it is possible to implement own custom caching layer without a separate middleware - Allows setting a keys directory for multi-key auth systems - Allows auth endpoints to be always updated with the Auth provider's public JWKs directory (often `.well-known/jwks.json`) which makes key rotations without disruptions possible Todo: - More tests.
Added /auth-keys-fn/* & /.well-known/jwks.json testing endpoints to the router.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## next #3826 +/- ##
==========================================
- Coverage 91.71% 90.79% -0.92%
==========================================
Files 160 164 +4
Lines 10195 10434 +239
Branches 2885 3061 +176
==========================================
+ Hits 9350 9474 +124
- Misses 844 959 +115
Partials 1 1 ☔ View full report in Codecov by Sentry. |
Note that the test code I removed some commented out tests to give a clearer picture (Now at 307 lines of test code). But they did remind me—we need some signed cookie unit tests if we're gonna make sure to support everything in |
Hi @Beyondo Thanks! I'll check this later. |
@yusukebe No problem! I originally named the new interface Take your time! I'm using the fork temporarily, so no rush at all. |
Hey @Code-Hex ! I'll review this later, but can you take a look at it too? |
@yusukebe I'm sorry I missed this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was mostly LGTM!
Also removed redundant "import type {} from '../..'" from 3 different files: - jwk/index.ts - jwt/index.ts - request-id/index.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@Code-Hex Thank you for the review! Hi, @Beyondo, Almost all are good, but the test coverage is decreased. Of course, it should not be 100%, but is there any chance to add more tests? |
Hey, @yusukebe ! I improved test coverage by around +15% focusing on testing the important parts, such as signed cookies and missing |
Thank you so much! If it's ready, please ping me. I'll review it. |
Note: Moved more code from `hono/jwk` to the `verifyFromJwks` function for backends that require JWK verification logic beyond just a middleware
@yusukebe Yo, everything, at least JWK-related, is covered & all hono tests pass. I think it should be decent enough for review now |
Thank you for your hard work! Almost done. I've left comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Hi @Beyondo Looks good. Thank you for your hard work! I'll merge it into the |
I am using this middleware and it works without any issues, thanks! My application has several endpoints that have optional authentication. However, this middleware throws an unauthenticated exception if there is no authentication available. Would it be possible to support this use-case somehow? Or am I doing something wrong? Also I don't know if I'm holding it wrong, but const payload = c.get("jwtPayload"); is reported as an error by the type checker ( Adding this to the d.ts of jwk fixes that: declare module 'hono' {
interface ContextVariableMap {
jwtPayload: unknown; // Maybe we can even be more precise with an object structure
}
} However, that fix won't work if there would be an optional jwtPayload. Edit: return createMiddleware<{
Variables: {
jwtPayload: JWTPayload | undefined;
};
}>(async function jwk(ctx, next) { |
@nikeee I would personally attempt to separate anonymous endpoints, but addressing the question—you could create a custom middleware, say const jwkOptional = (options, init) => {
const jwkMiddleware = jwk(options, init);
return async (c, next) => {
try {
await jwkMiddleware(c, next);
} catch (err) {
await next();
}
};
}; With types const jwkOptional = (options: {
keys?: HonoJsonWebKey[] | (() => Promise < HonoJsonWebKey[] > );
jwks_uri?: string;
cookie?: string | {
key: string;
secret?: string | BufferSource;
prefixOptions?: CookiePrefixOptions;
};
}, init?: RequestInit) => {
const jwkMiddleware = jwk(options, init);
return async (c: Context, next: Next) => {
try {
await jwkMiddleware(c, next);
} catch (err) {
await next();
}
};
}; You could also modify it to set a boolean such as |
I thought about that approach, but I ended up doing it in reverse: rewriting JWK to make it This works for the thing I'm currently building, but recommending this approach to hono's library itself would be inconsistent with the JWT middleware. Regarding the context: Do you think it is useful to change the return of jwk to something like this? return createMiddleware<{
Variables: {
jwtPayload: JWTPayload;
};
}>(async function jwk(ctx, next) { |
Hello, I recently needed a JWK middleware for my projects, but I figured contributing it to hono has the potential to save me and others a lot of time.
Middleware Features:
options.keys
to a static array of public keysHonoJsonWebKey[]
in code.options.keys
to an async function that returns aPromise<HonoJsonWebKey[]>
for flexibilityoptions.jwks_uri
to fetch JWKs from a URI, after which it appends those fetched keys to providedkeys
if anyinit
parameter (only used forjwks_uri
)—useful if your host supports caching through custom init options.Added extra:
JwtHeaderRequiresKid
exception. Since the middleware requires presence of akid
field in the header in order to select the correct key.Jwt.verifyFromJwks
util function (batteries included).Addressed issues:
Other code changes:
JsonWebKey
to havekid?: string
(This is a standard: https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.4)kid?: string
to TokenHeaderJwt.sign(payload, privateKey, alg)
whereprivateKey.alg
was always ignored and onlyalg
parameter was considered.Suggestion: Removal of the default
'HS256'
value fromJwt.sign
and makealg
optional, so that if neitherprivateKey.alg
noralg
is defined, throw an error. My 'philosophy' for this is that it would be misleading for hono to display= 'HS256'
when the privateKey can internally have a different algorithm, such as when it is of typeJsonWebKey
orCryptoKey
(Though we only care aboutJsonWebKey
here). Makingalg
a "fallback" explicit parameter could be better.Example Usage:
Using
jwks_uri
Using
jwks_uri
and an optionalinit
Using
keys
from an arrayUsing
keys
as an async function (custom logic / caching)Tests:
I adapted a lot from JWT's own units tests to make sure some basic JWT validation exists, then added the actual validation for the JWK middleware's own functionality which is mostly: (1) getting keys through various methods, (2) selecting the appropriate JWK based on
kid
(without fully decoding the JWT—only the header), and then (3) verifying using the utilityJwt.verify
. Step 2 and 3 are done byJwt.verifyFromJwks
.JWK Middleware Test
Full Test
bun run format:fix && bun run lint:fix
to format the code (Done)Feel free to contribute more unit tests, extend and/or modify for the better.