@@ -12,6 +12,7 @@ Additional optional dependencies may be needed, all optional dependencies are:
12
12
13
13
- ` react-router `
14
14
- ` @edgefirst-dev/batcher `
15
+ - ` @edgefirst-dev/jwt `
15
16
- ` @edgefirst-dev/server-timing `
16
17
- ` @oslojs/crypto `
17
18
- ` @oslojs/encoding `
@@ -25,7 +26,7 @@ The utils that require an extra optional dependency mention it in their document
25
26
If you want to install them all run:
26
27
27
28
``` sh
28
- npm add @edgefirst-dev/batcher @edgefirst-dev/server-timing @oslojs/crypto @oslojs/encoding is-ip intl-parse-accept-language zod
29
+ npm add @edgefirst-dev/batcher @edgefirst-dev/jwt @edgefirst-dev/ server-timing @oslojs/crypto @oslojs/encoding is-ip intl-parse-accept-language zod
29
30
```
30
31
31
32
React and React Router packages should be already installed in your project.
@@ -2089,15 +2090,15 @@ let [sessionMiddleware, getSession] =
2089
2090
Then you can use the ` sessionMiddleware ` in your ` app/root.tsx ` function.
2090
2091
2091
2092
``` ts
2092
- import { sessionMiddleware } from " ~/session.server" ;
2093
+ import { sessionMiddleware } from " ~/middleware/ session.server" ;
2093
2094
2094
2095
export const unstable_middleware = [sessionMiddleware ];
2095
2096
```
2096
2097
2097
2098
And you can use the ` getSession ` function in your loaders to get the session object.
2098
2099
2099
2100
``` ts
2100
- import { getSession } from " ~/session.server" ;
2101
+ import { getSession } from " ~/middleware/ session.server" ;
2101
2102
2102
2103
export async function loader({ context }: Route .LoaderArgs ) {
2103
2104
let session = await getSession (context );
@@ -2153,7 +2154,7 @@ export const [loggerMiddleware] = unstable_createLoggerMiddleware();
2153
2154
To use it, you need to add it to the ` unstable_middleware ` array in your ` app/root.tsx ` file.
2154
2155
2155
2156
``` ts
2156
- import { loggerMiddleware } from " ~/logger.server" ;
2157
+ import { loggerMiddleware } from " ~/middleware/ logger.server" ;
2157
2158
export const unstable_middleware = [loggerMiddleware ];
2158
2159
```
2159
2160
@@ -2190,15 +2191,15 @@ export const [serverTimingMiddleware, getTimingCollector] =
2190
2191
To use it, you need to add it to the ` unstable_middleware ` array in your ` app/root.tsx ` file.
2191
2192
2192
2193
``` ts
2193
- import { serverTimingMiddleware } from " ~/server-timing.server" ;
2194
+ import { serverTimingMiddleware } from " ~/middleware/ server-timing.server" ;
2194
2195
2195
2196
export const unstable_middleware = [serverTimingMiddleware ];
2196
2197
```
2197
2198
2198
2199
And you can use the ` getTimingCollector ` function in your loaders and actions to add timings to the response.
2199
2200
2200
2201
``` ts
2201
- import { getTimingCollector } from " ~/server-timing.server" ;
2202
+ import { getTimingCollector } from " ~/middleware/ server-timing.server" ;
2202
2203
2203
2204
export async function loader({ request }: LoaderFunctionArgs ) {
2204
2205
let collector = getTimingCollector ();
@@ -2229,14 +2230,14 @@ export const [singletonMiddleware, getSingleton] =
2229
2230
To use it, you need to add it to the ` unstable_middleware ` array in the route where you want to use it.
2230
2231
2231
2232
``` ts
2232
- import { singletonMiddleware } from " ~/singleton.server" ;
2233
+ import { singletonMiddleware } from " ~/middleware/ singleton.server" ;
2233
2234
export const unstable_middleware = [singletonMiddleware ];
2234
2235
```
2235
2236
2236
2237
And you can use the ` getSingleton ` function in your loaders to get the singleton object.
2237
2238
2238
2239
``` ts
2239
- import { getSingleton } from " ~/singleton.server" ;
2240
+ import { getSingleton } from " ~/middleware/ singleton.server" ;
2240
2241
2241
2242
export async function loader({ request }: LoaderFunctionArgs ) {
2242
2243
let singleton = getSingleton ();
@@ -2269,7 +2270,7 @@ And use it in a route like this.
2269
2270
import {
2270
2271
singletonMiddleware ,
2271
2272
anotherSingletonMiddleware ,
2272
- } from " ~/singleton.server" ;
2273
+ } from " ~/middleware/ singleton.server" ;
2273
2274
2274
2275
export const unstable_middleware = [
2275
2276
singletonMiddleware ,
@@ -2296,14 +2297,14 @@ export const [batcherMiddleware, getBatcher] =
2296
2297
To use it, you need to add it to the ` unstable_middleware ` array in the route where you want to use it.
2297
2298
2298
2299
``` ts
2299
- import { batcherMiddleware } from " ~/batcher.server" ;
2300
+ import { batcherMiddleware } from " ~/middleware/ batcher.server" ;
2300
2301
export const unstable_middleware = [batcherMiddleware ];
2301
2302
```
2302
2303
2303
2304
And you can use the ` getBatcher ` function in your loaders to get the batcher object.
2304
2305
2305
2306
``` ts
2306
- import { getBatcher } from " ~/batcher.server" ;
2307
+ import { getBatcher } from " ~/middleware/ batcher.server" ;
2307
2308
2308
2309
export async function loader({ request }: LoaderFunctionArgs ) {
2309
2310
let batcher = getBatcher ();
@@ -2328,15 +2329,15 @@ export const [contextStorageMiddleware, getContext, getRequest] =
2328
2329
To use it, you need to add it to the ` unstable_middleware ` array in your ` app/root.tsx ` file.
2329
2330
2330
2331
``` ts
2331
- import { contextStorageMiddleware } from " ~/context-storage.server" ;
2332
+ import { contextStorageMiddleware } from " ~/middleware/ context-storage.server" ;
2332
2333
2333
2334
export const unstable_middleware = [contextStorageMiddleware ];
2334
2335
```
2335
2336
2336
2337
And you can use the ` getContext ` and ` getRequest ` functions in your function to get the context and request objects.
2337
2338
2338
2339
``` ts
2339
- import { getContext , getRequest } from " ~/context-storage.server" ;
2340
+ import { getContext , getRequest } from " ~/middleware/ context-storage.server" ;
2340
2341
2341
2342
export async function doSomething() {
2342
2343
let context = getContext ();
@@ -2361,15 +2362,15 @@ export const [requestIDMiddleware, getRequestID] =
2361
2362
To use it, you need to add it to the ` unstable_middleware ` array in your ` app/root.tsx ` file.
2362
2363
2363
2364
``` ts
2364
- import { requestIDMiddleware } from " ~/request-id.server" ;
2365
+ import { requestIDMiddleware } from " ~/middleware/ request-id.server" ;
2365
2366
2366
2367
export const unstable_middleware = [requestIDMiddleware ];
2367
2368
```
2368
2369
2369
2370
And you can use the ` getRequestID ` function in your loaders, actions, and other middleware to get the request ID.
2370
2371
2371
2372
``` ts
2372
- import { getRequestID } from " ~/request-id.server" ;
2373
+ import { getRequestID } from " ~/middleware/ request-id.server" ;
2373
2374
2374
2375
export async function loader({ request }: LoaderFunctionArgs ) {
2375
2376
let requestID = getRequestID ();
@@ -2410,7 +2411,7 @@ export const [requestIDMiddleware, getRequestID] =
2410
2411
2411
2412
The Basic Auth middleware let's you add a basic authentication to your routes, this can be useful to protect routes that need to be private.
2412
2413
2413
- > [ !WARN ]
2414
+ > [ !WARNING ]
2414
2415
> Basic Auth is not secure by itself, it should be used with HTTPS to ensure the username and password are encrypted. Do not use it to protect sensitive data, use a more secure method instead.
2415
2416
2416
2417
``` ts
@@ -2424,7 +2425,7 @@ export const [basicAuthMiddleware] = unstable_createBasicAuthMiddleware({
2424
2425
To use it, you need to add it to the ` unstable_middleware ` array in the route where you want to use it.
2425
2426
2426
2427
``` ts
2427
- import { basicAuthMiddleware } from " ~/basic-auth.server" ;
2428
+ import { basicAuthMiddleware } from " ~/middleware/ basic-auth.server" ;
2428
2429
export const unstable_middleware = [basicAuthMiddleware ];
2429
2430
```
2430
2431
@@ -2522,6 +2523,158 @@ WWW-Authenticate: Basic realm="My Realm"
2522
2523
{"message":"Invalid username or password"}
2523
2524
```
2524
2525
2526
+ #### JWK Auth Middleware
2527
+
2528
+ > [ !NOTE]
2529
+ > This depends on ` @edgefirst-dev/jwt ` .
2530
+
2531
+ 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.
2532
+
2533
+ > [ !WARNING]
2534
+ > JWK Auth is more secure than Basic Auth, but it should be used with HTTPS to ensure the token is encrypted.
2535
+
2536
+ ``` ts
2537
+ import { unstable_createJWKAuthMiddleware } from " remix-utils/middleware/jwk-auth" ;
2538
+
2539
+ export const [jwkAuthMiddleware, getJWTPayload] =
2540
+ unstable_createJWKAuthMiddleware ({
2541
+ jwksUri: " https://auth.example.com/.well-known/jwks.json" ,
2542
+ });
2543
+ ```
2544
+
2545
+ The ` jwksUri ` option let's you set the URL to the JWKS endpoint, this is the URL where the public keys are stored.
2546
+
2547
+ To use the middleware, you need to add it to the ` unstable_middleware ` array in the route where you want to use it.
2548
+
2549
+ ``` ts
2550
+ import { jwkAuthMiddleware } from " ~/middleware/jwk-auth" ;
2551
+ export const unstable_middleware = [jwkAuthMiddleware ];
2552
+ ```
2553
+
2554
+ Now, when you access the route it will check the JWT token in the ` Authorization ` header.
2555
+
2556
+ In case of an invalid token the middleware will return a ` 401 ` status code with a ` WWW-Authenticate ` header.
2557
+
2558
+ ``` http
2559
+ HTTP/1.1 401 Unauthorized
2560
+ WWW-Authenticate: Bearer realm="Secure Area"
2561
+
2562
+ Unauthorized
2563
+ ```
2564
+
2565
+ The ` realm ` option let's you set the realm for the authentication, this is the name of the protected area.
2566
+
2567
+ ``` ts
2568
+ import { unstable_createJWKAuthMiddleware } from " remix-utils/middleware/jwk-auth" ;
2569
+
2570
+ export const [jwkAuthMiddleware] = unstable_createJWKAuthMiddleware ({
2571
+ realm: " My Realm" ,
2572
+ jwksUri: " https://auth.example.com/.well-known/jwks.json" ,
2573
+ });
2574
+ ```
2575
+
2576
+ If you want to customize the message sent when the token is invalid you can use the ` invalidTokenMessage ` option.
2577
+
2578
+ ``` ts
2579
+ import { unstable_createJWKAuthMiddleware } from " remix-utils/middleware/jwk-auth" ;
2580
+
2581
+ export const [jwkAuthMiddleware] = unstable_createJWKAuthMiddleware ({
2582
+ invalidTokenMessage: " Invalid token" ,
2583
+ jwksUri: " https://auth.example.com/.well-known/jwks.json" ,
2584
+ });
2585
+ ```
2586
+
2587
+ And this will be the response when the token is invalid.
2588
+
2589
+ ``` http
2590
+ HTTP/1.1 401 Unauthorized
2591
+ WWW-Authenticate: Bearer realm="Secure Area"
2592
+
2593
+ Invalid token
2594
+ ```
2595
+
2596
+ You can also customize the ` invalidTokenMessage ` by passing a function which will receive the Request and context objects.
2597
+
2598
+ ``` ts
2599
+ import { unstable_createJWKAuthMiddleware } from " remix-utils/middleware/jwk-auth" ;
2600
+
2601
+ export const [jwkAuthMiddleware] = unstable_createJWKAuthMiddleware ({
2602
+ invalidTokenMessage({ request , context }) {
2603
+ // do something with request or context here
2604
+ return { message: ` Invalid token ` };
2605
+ },
2606
+ jwksUri: " https://auth.example.com/.well-known/jwks.json" ,
2607
+ });
2608
+ ```
2609
+
2610
+ 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.
2611
+
2612
+ ``` http
2613
+ HTTP/1.1 401 Unauthorized
2614
+ WWW-Authenticate: Bearer realm="Secure Area"
2615
+
2616
+ {"message":"Invalid token"}
2617
+ ```
2618
+
2619
+ If you want to get the JWT payload in your loaders, actions, or other middleware you can use the ` getJWTPayload ` function.
2620
+
2621
+ ``` ts
2622
+ import { getJWTPayload } from " ~/middleware/jwk-auth.server" ;
2623
+
2624
+ export async function loader({ request }: LoaderFunctionArgs ) {
2625
+ let payload = getJWTPayload ();
2626
+ // ...
2627
+ }
2628
+ ```
2629
+
2630
+ And you can use the payload to get the subject, scope, issuer, audience, or any other information stored in the token.
2631
+
2632
+ ##### With a Custom Header
2633
+
2634
+ 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.
2635
+
2636
+ ``` ts
2637
+ import { unstable_createJWKAuthMiddleware } from " remix-utils/middleware/jwk-auth" ;
2638
+
2639
+ export const [jwkAuthMiddleware, getJWTPayload] =
2640
+ unstable_createJWKAuthMiddleware ({ header: " X-API-Key" });
2641
+ ```
2642
+
2643
+ 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.
2644
+
2645
+ ``` ts
2646
+ import { jwkAuthMiddleware } from " ~/middleware/jwk-auth" ;
2647
+
2648
+ export const unstable_middleware = [jwkAuthMiddleware ];
2649
+ ```
2650
+
2651
+ ##### With a Cookie
2652
+
2653
+ 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.
2654
+
2655
+ ``` ts
2656
+ import { unstable_createJWKAuthMiddleware } from " remix-utils/middleware/jwk-auth" ;
2657
+ import { createCookie } from " react-router" ;
2658
+
2659
+ export const cookie = createCookie (" jwt" , {
2660
+ path: " /" ,
2661
+ sameSite: " lax" ,
2662
+ httpOnly: true ,
2663
+ secure: process .env .NODE_ENV === " true" ,
2664
+ });
2665
+
2666
+ export const [jwkAuthMiddleware, getJWTPayload] =
2667
+ unstable_createJWKAuthMiddleware ({ cookie });
2668
+ ```
2669
+
2670
+ 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.
2671
+
2672
+ ``` ts
2673
+ import { jwkAuthMiddleware } from " ~/middleware/jwk-auth" ;
2674
+
2675
+ export const unstable_middleware = [jwkAuthMiddleware ];
2676
+ ```
2677
+
2525
2678
## Author
2526
2679
2527
2680
- [ Sergio Xalambrí] ( https://sergiodxa.com )
0 commit comments