remix-utils
Version:
This package contains simple utility functions to use with [React Router](https://reactrouter.com/).
233 lines (232 loc) • 8.35 kB
TypeScript
/**
* > This depends on `@edgefirst-dev/jwt`.
*
* The JWK Auth middleware let's you add a JSON Web Key authentication to your routes, this can be useful to protect routes that need to be private and will be accessed by other services.
*
* > **Warning**: JWK Auth is more secure than Basic Auth, but it should be used with HTTPS to ensure the token is encrypted.
*
* ```ts
* import { unstable_createJWKAuthMiddleware } from "remix-utils/middleware/jwk-auth";
*
* export const [jwkAuthMiddleware, getJWTPayload] =
* unstable_createJWKAuthMiddleware({
* jwksUri: "https://auth.example.com/.well-known/jwks.json",
* });
* ```
*
* The `jwksUri` option let's you set the URL to the JWKS endpoint, this is the URL where the public keys are stored.
*
* To use the middleware, you need to add it to the `unstable_middleware` array in the route where you want to use it.
*
* ```ts
* import { jwkAuthMiddleware } from "~/middleware/jwk-auth";
* export const unstable_middleware = [jwkAuthMiddleware];
* ```
*
* Now, when you access the route it will check the JWT token in the `Authorization` header.
*
* In case of an invalid token the middleware will return a `401` status code with a `WWW-Authenticate` header.
*
* ```http
* HTTP/1.1 401 Unauthorized
* WWW-Authenticate: Bearer realm="Secure Area"
*
* Unauthorized
* ```
*
* The `realm` option let's you set the realm for the authentication, this is the name of the protected area.
*
* ```ts
* import { unstable_createJWKAuthMiddleware } from "remix-utils/middleware/jwk-auth";
*
* export const [jwkAuthMiddleware] = unstable_createJWKAuthMiddleware({
* realm: "My Realm",
* jwksUri: "https://auth.example.com/.well-known/jwks.json",
* });
* ```
*
* If you want to customize the message sent when the token is invalid you can use the `invalidTokenMessage` option.
*
* ```ts
* import { unstable_createJWKAuthMiddleware } from "remix-utils/middleware/jwk-auth";
*
* export const [jwkAuthMiddleware] = unstable_createJWKAuthMiddleware({
* invalidTokenMessage: "Invalid token",
* jwksUri: "https://auth.example.com/.well-known/jwks.json",
* });
* ```
*
* And this will be the response when the token is invalid.
*
* ```http
* HTTP/1.1 401 Unauthorized
* WWW-Authenticate: Bearer realm="Secure Area"
*
* Invalid token
* ```
*
* You can also customize the `invalidTokenMessage` by passing a function which will receive the Request and context objects.
*
* ```ts
* import { unstable_createJWKAuthMiddleware } from "remix-utils/middleware/jwk-auth";
*
* export const [jwkAuthMiddleware] = unstable_createJWKAuthMiddleware({
* invalidTokenMessage({ request, context }) {
* // do something with request or context here
* return { message: `Invalid token` };
* },
* jwksUri: "https://auth.example.com/.well-known/jwks.json",
* });
* ```
*
* In both cases, with a hard-coded value or a function, the invalid message can be a string or an object, if it's an object it will be converted to JSON.
*
* ```http
* HTTP/1.1 401 Unauthorized
* WWW-Authenticate: Bearer realm="Secure Area"
*
* {"message":"Invalid token"}
* ```
*
* If you want to get the JWT payload in your loaders, actions, or other middleware you can use the `getJWTPayload` function.
*
* ```ts
* import { getJWTPayload } from "~/middleware/jwk-auth.server";
*
* export async function loader({ request }: LoaderFunctionArgs) {
* let payload = getJWTPayload();
* // ...
* }
* ```
*
* And you can use the payload to get the subject, scope, issuer, audience, or any other information stored in the token.
*
* ## With a Custom Header
*
* If your app receives the JWT in a custom header instead of the `Authorization` header you can tell the middleware to look for the token in that header.
*
* ```ts
* import { unstable_createJWKAuthMiddleware } from "remix-utils/middleware/jwk-auth";
*
* export const [jwkAuthMiddleware, getJWTPayload] =
* unstable_createJWKAuthMiddleware({ header: "X-API-Key" });
* ```
*
* Now use the middleware as usual, but now instead of looking for the token in the `Authorization` header it will look for it in the `X-API-Key` header.
*
* ```ts
* import { jwkAuthMiddleware } from "~/middleware/jwk-auth";
*
* export const unstable_middleware = [jwkAuthMiddleware];
* ```
*
* ## With a Cookie
*
* If you save a JWT in a cookie using React Router's Cookie API, you can tell the middleware to look for the token in the cookie instead of the `Authorization` header.
*
* ```ts
* import { unstable_createJWKAuthMiddleware } from "remix-utils/middleware/jwk-auth";
* import { createCookie } from "react-router";
*
* export const cookie = createCookie("jwt", {
* path: "/",
* sameSite: "lax",
* httpOnly: true,
* secure: process.env.NODE_ENV === "true",
* });
*
* export const [jwkAuthMiddleware, getJWTPayload] =
* unstable_createJWKAuthMiddleware({ cookie });
* ```
*
* Then use the middleware as usual, but now instead of looking for the token in the `Authorization` header it will look for it in the cookie.
*
* ```ts
* import { jwkAuthMiddleware } from "~/middleware/jwk-auth";
*
* export const unstable_middleware = [jwkAuthMiddleware];
* ```
* @author [Sergio Xalambrí](https://sergiodxa.com)
* @module Middleware/JWK Auth
*/
import { JWK, JWT } from "@edgefirst-dev/jwt";
import { type Cookie, type unstable_MiddlewareFunction, unstable_RouterContextProvider } from "react-router";
import type { unstable_MiddlewareGetter } from "./utils.js";
export declare function unstable_createJWKAuthMiddleware({ jwksUri, realm, alg, invalidUserMessage, ...options }: unstable_createBearerAuthMiddleware.Options): unstable_createBearerAuthMiddleware.ReturnType;
export declare namespace unstable_createBearerAuthMiddleware {
type Args = {
request: Request;
context: unstable_RouterContextProvider;
};
type MessageFunction = (args: Args) => string | object | Promise<string | object>;
interface BaseOptions {
/**
* The URL of the JWKS endpoint.
* @example
* "https://auth.example.com/.well-known/jwks.json"
*/
jwksUri: ConstructorParameters<typeof URL>[0];
/**
* The algorithm to use for verifying the JWT signature.
* @default "ES256"
*/
alg?: JWK.Algoritm;
/**
* The message to return when the user is invalid.
*
* If a function is provided, it will be called with the request and context
* as arguments.
*
* If the function returns a string, it will be used as the message.
*
* If the function returns an object, it will be serialized as JSON and used
* as the response body.
*
* @default "Unauthorized"
* @example
* "Invalid user"
* (args) => `Invalid user: ${args.request.headers.get("X-User")}`
* async (args) => {
* let user = await getUser(args.context);
* return `Invalid user: ${user}`;
* }
* { error: "Invalid user" }
* (args) => ({
* error: `Invalid user: ${args.request.headers.get("X-User")}`
* })
* async (args) => {
* let user = await getUser(args.context);
* return { error: `Invalid user: ${user}` };
* }
*/
invalidUserMessage?: string | object | MessageFunction;
/**
* The domain name of the realm, as part of the returned WWW-Authenticate
* challenge header.
*
* @default "Secure Area"
*/
realm?: string;
verifyOptions?: JWT.VerifyOptions;
}
interface HeaderOptions extends BaseOptions {
/**
* The name of the header to use for the bearer token.
* @default "Authorization"
*/
headerName?: string;
}
interface CookieOptions extends BaseOptions {
/**
* The cookie to use for the bearer token.
*
* If provided the cookie will be parsed to try to extract the JWT.
*/
cookie: Cookie;
}
type Options = HeaderOptions | CookieOptions;
type ReturnType = [
unstable_MiddlewareFunction<Response>,
unstable_MiddlewareGetter<JWT>
];
}