Skip to content

Commit 53cc96c

Browse files
committed
actually make the thing
1 parent aa0a05e commit 53cc96c

File tree

3 files changed

+116
-24
lines changed

3 files changed

+116
-24
lines changed

.github/workflows/charts.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Release Charts
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
release:
10+
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
11+
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
12+
permissions:
13+
contents: write
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v3
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Configure Git
22+
run: |
23+
git config user.name "$GITHUB_ACTOR"
24+
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
25+
26+
- name: Install Helm
27+
uses: azure/setup-helm@v3
28+
29+
- name: Run chart-releaser
30+
uses: helm/chart-releaser-action@v1.6.0
31+
env:
32+
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

README.md

+36-3
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,44 @@ This is a simple way to validate JSON Web Keys (JWK) in Nginx using [Nginx Exter
66

77
Keep in mind that this service is purely meant to **validate** already signed tokens. It does not sign tokens or provide any other functionality. Your IdP should do more granular access control, and this service should only be used to validate the token signature.
88

9-
Ideally, your IdP has [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) compliant discovery endpoint, and it at least exposes the `issuer` and `jwks_uri` fields. If your IdP does not provide a OIDC Discovery endpoint, you can still use this service by providing the JWK URI and issuer manually.
9+
Ideally, your IdP has a [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) compliant discovery endpoint, and it at least exposes the `issuer` and `jwks_uri` fields. If your IdP does not provide a OIDC Discovery endpoint, you can still use this service by providing the JWK URI and issuer manually.
1010

11-
## Usage
11+
## Setup
1212

13-
> **Note**
13+
> [!NOTE]
1414
> Using Kubernetes? Use [Helm](https://helm.sh/) to deploy this service. Check out the [Helm chart]().
1515
1616
1. Clone this repository
17+
2. Find your IdP's OIDC Discovery endpoint or JWK URI. For example, Google's OIDC Discovery endpoint is `https://accounts.google.com/.well-known/openid-configuration`, it's JWK URI is `https://www.googleapis.com/oauth2/v3/certs`.
18+
- If your IdP does not provide a OIDC Discovery endpoint, you should set a default issuer and JWK URI manually. The issuer is optional, but recommended.
19+
3. Run the service with the following environment variables:
20+
- `OIDC_DISCOVERY_URI`: The URI to the OIDC Discovery endpoint. **Recommended way to configure**. For example, `https://accounts.google.com/.well-known/openid-configuration`.
21+
- `JWK_URI`: The URI to the JWK set. For example, `https://www.googleapis.com/oauth2/v3/certs`. Only required if `OIDC_DISCOVERY_URI` is not set.
22+
- `JWT_ISSUER`: The default issuer of the JWT. Optional. Gets automatically populated if `OIDC_DISCOVERY_URI` is set.
23+
- `JWT_AUDIENCE`: The audience tag that the JWT should have. Optional.
24+
- `JWT_HEADER`: The header to look for the JWT in. Default is `Authorization`.
25+
- `PORT`: The port to listen on. Default is `8080`.
26+
27+
`JWT_AUDIENCE` and `JWT_ISSUER` can be overridden by using the `aud` and `iss` query parameters in the request, this is useful if you have multiple audiences or issuers.
28+
29+
# Usage
30+
31+
Since this service is meant to be used with Nginx External Authentication, you should configure your Nginx to use this service as an external auth provider. Here is an example configuration:
32+
33+
```nginx
34+
location / {
35+
auth_request /auth;
36+
error_page 401 = /auth_error;
37+
38+
# Your application
39+
proxy_pass http://your_application;
40+
}
41+
42+
location = /auth {
43+
internal;
44+
proxy_pass http://localhost:8080;
45+
proxy_pass_request_body off;
46+
proxy_set_header Content-Length "";
47+
proxy_set_header X-Original-URI $request_uri;
48+
}
49+
```

src/index.mts

+48-21
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
import Fastify from "fastify";
2-
import { createRemoteJWKSet } from "jose";
3-
import { JWTPayload, jwtVerify } from "jose";
2+
import { JWTPayload, jwtVerify, createRemoteJWKSet } from "jose";
3+
4+
if (!process.env.JWKS_URI && !process.env.OIDC_DISCOVERY_URI) {
5+
console.error("JWKS_URI is required if OIDC_DISCOVERY_URI is not provided");
6+
process.exit(1);
7+
}
8+
9+
if (process.env.OIDC_DISCOVERY_URI) {
10+
const discovery = await fetch(process.env.OIDC_DISCOVERY_URI);
11+
const discoveryJson = (await discovery.json()) as {
12+
jwks_uri: string;
13+
issuer: string;
14+
};
15+
process.env.JWKS_URI = discoveryJson.jwks_uri;
16+
process.env.JWT_ISSUER = discoveryJson.issuer;
17+
}
418

519
const fastify = Fastify({
620
logger: true,
721
});
822

9-
const JWKS = createRemoteJWKSet(
10-
new URL("https://dougley.cloudflareaccess.com/cdn-cgi/access/certs"),
11-
);
23+
const JWKS = createRemoteJWKSet(new URL(process.env.JWKS_URI!));
1224

1325
fastify.get<{
1426
Querystring: {
@@ -19,21 +31,36 @@ fastify.get<{
1931
authorization: string;
2032
};
2133
}>("/", async (request, reply) => {
22-
const { payload, protectedHeader } = await jwtVerify(
23-
request.headers.authorization,
24-
JWKS,
25-
{
26-
issuer: request.query.iss,
27-
audience: request.query.aud,
28-
},
29-
);
30-
return { payload, protectedHeader };
31-
});
32-
33-
fastify.listen({ port: 8080, host: "0.0.0.0" }, function (err, address) {
34-
if (err) {
35-
fastify.log.error(err);
36-
process.exit(1);
34+
const header = process.env.JWT_HEADER ?? "authorization";
35+
if (!request.headers[header]) {
36+
reply.status(401).send({ error: "Unauthorized" });
37+
return;
38+
}
39+
request.headers.authorization = Array.isArray(request.headers[header])
40+
? request.headers[header]![0]
41+
: (request.headers[header] as string);
42+
const jwt = request.headers.authorization.startsWith("Bearer ")
43+
? request.headers.authorization.slice(7)
44+
: request.headers.authorization;
45+
try {
46+
const { payload, protectedHeader } = await jwtVerify(jwt, JWKS, {
47+
issuer: request.query.iss ?? process.env.JWT_ISSUER ?? undefined,
48+
audience: request.query.aud ?? process.env.JWT_AUDIENCE ?? undefined,
49+
});
50+
return { payload, protectedHeader };
51+
} catch (e) {
52+
console.log("Verification failed", e);
53+
reply.status(401).send({ error: "Unauthorized" });
3754
}
38-
fastify.log.info(`server listening on ${address}`);
3955
});
56+
57+
fastify.listen(
58+
{ port: +(process.env.PORT ?? 8080), host: "0.0.0.0" },
59+
function (err, address) {
60+
if (err) {
61+
fastify.log.error(err);
62+
process.exit(1);
63+
}
64+
fastify.log.info(`server listening on ${address}`);
65+
},
66+
);

0 commit comments

Comments
 (0)