Skip to content

Commit 54df278

Browse files
identity: add single page app example (#112)
* identity: add single page app example * Update content/docs/topics/getting-users-identity.md Co-authored-by: cmo-pomerium <[email protected]> * Update content/docs/topics/getting-users-identity.md Co-authored-by: cmo-pomerium <[email protected]> Co-authored-by: cmo-pomerium <[email protected]>
1 parent b5c57ef commit 54df278

File tree

1 file changed

+66
-25
lines changed

1 file changed

+66
-25
lines changed

content/docs/topics/getting-users-identity.md

+66-25
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@ To secure your app with signed headers, you'll need the following:
1414

1515
## Verification
1616

17-
If a [signing key] is set, the user's associated identity information will be included in a signed attestation JWT that will be added to each requests's upstream header `X-Pomerium-Jwt-Assertion`. You should verify that the JWT contains at least the following claims:
18-
19-
| [JWT] | description |
20-
| :------: | ----------------------------------------------------------------------------------------- |
21-
| `exp` | Expiration time in seconds since the UNIX epoch. Allow 1 minute for skew. |
22-
| `iat` | Issued-at time in seconds since the UNIX epoch. Allow 1 minute for skew. |
23-
| `aud` | The client's final domain e.g. `httpbin.corp.example.com`. |
24-
| `iss` | Issuer must be the URL of your authentication domain e.g. `authenticate.corp.example`. |
25-
| `sub` | Subject is the user's id. Can be used instead of the `X-Pomerium-Claim-Sub` header. |
26-
| `email` | Email is the user's email. Can be used instead of the `X-Pomerium-Claim-Email` header. |
27-
| `groups` | Groups is the user's groups. Can be used instead of the `X-Pomerium-Claim-Groups` header. |
17+
If a [signing key] is set, the user's associated identity information will be included in a signed attestation JWT that will be added to each requests's upstream header `X-Pomerium-Jwt-Assertion`. The signed attestation JWT is also available at the special `/.pomerium/jwt` endpoint of any URL handled by Pomerium.
18+
19+
You should verify that the JWT contains at least the following claims:
20+
21+
| [JWT] | description |
22+
| :------: | ----------------------------------------------------------------------------------------- |
23+
| `exp` | Expiration time in seconds since the UNIX epoch. Allow 1 minute for skew. |
24+
| `iat` | Issued-at time in seconds since the UNIX epoch. Allow 1 minute for skew. |
25+
| `aud` | The client's final domain e.g. `httpbin.corp.example.com`. |
26+
| `iss` | Issuer must be the URL of your authentication domain e.g. `authenticate.corp.example`. |
27+
| `sub` | Subject is the user's id. Can be used instead of the `X-Pomerium-Claim-Sub` header. |
28+
| `email` | Email is the user's email. Can be used instead of the `X-Pomerium-Claim-Email` header. |
29+
| `groups` | Groups is the user's groups. Can be used instead of the `X-Pomerium-Claim-Groups` header. |
2830

2931
The attestation JWT's signature can be verified using the public key which can be retrieved at Pomerium's `/.well-known/pomerium/jwks.json` endpoint which lives on the authenticate service. A `jwks_uri` is useful when integrating with other systems like [istio](https://istio.io/docs/reference/config/security/istio.authentication.v1alpha1/). For example:
3032

@@ -63,38 +65,77 @@ curl https://authenticate.int.example.com/.well-known/pomerium/jwks.json | jq
6365
}
6466
```
6567

68+
### Verification in a Single-Page Application
69+
70+
A single-page javascript application can verify the JWT using a fetch to `/.pomerium/jwt` and a JWT library like [`jose`](https://github.com/panva/jose).
71+
72+
```html
73+
<!DOCTYPE html>
74+
<html>
75+
<head>
76+
<script type="module">
77+
import {
78+
decodeJwt,
79+
createRemoteJWKSet,
80+
jwtVerify,
81+
} from "https://cdn.skypack.dev/jose";
82+
83+
async function main() {
84+
const result1 = await fetch("/.pomerium/jwt");
85+
const jwt = await result1.text();
86+
const claims = decodeJwt(jwt);
87+
console.log("Unverified Claims:", claims);
88+
89+
const jwksURL =
90+
"https://" + claims.iss + "/.well-known/pomerium/jwks.json";
91+
const jwks = await createRemoteJWKSet(new URL(jwksURL));
92+
93+
const { payload } = await jwtVerify(jwt, jwks, {
94+
audience: claims.aud,
95+
issuer: claims.iss,
96+
});
97+
console.log("Verified Claims:", payload);
98+
}
99+
100+
main();
101+
</script>
102+
</head>
103+
<body></body>
104+
</html>
105+
```
106+
66107
### Manual verification
67108

68109
Though you will very likely be verifying signed-headers programmatically in your application's middleware, and using a third-party JWT library, if you are new to JWT it may be helpful to show what manual verification looks like.
69110

70-
1. Provide pomerium with a base64 encoded Elliptic Curve ([NIST P-256] aka [secp256r1] aka prime256v1) Private Key. In production, you'd likely want to get these from your KMS.
111+
1. Provide Pomerium with a base64 encoded Elliptic Curve ([NIST P-256] aka [secp256r1] aka prime256v1) Private Key. In production, you'd likely want to get these from your KMS.
71112

72-
```bash
73-
openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem
74-
openssl ec -in ec_private.pem -pubout -out ec_public.pem
75-
# careful! this will output your private key in terminal
76-
cat ec_private.pem | base64
77-
```
113+
```bash
114+
openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem
115+
openssl ec -in ec_private.pem -pubout -out ec_public.pem
116+
# careful! this will output your private key in terminal
117+
cat ec_private.pem | base64
118+
```
78119

79-
Copy the base64 encoded value of your private key to `pomerium-proxy`'s environmental configuration variable `SIGNING_KEY`.
120+
Copy the base64 encoded value of your private key to `pomerium-proxy`'s environmental configuration variable `SIGNING_KEY`.
80121

81-
```bash
82-
SIGNING_KEY=ZxqyyIPPX0oWrrOwsxXgl0hHnTx3mBVhQ2kvW1YB4MM=
83-
```
122+
```bash
123+
SIGNING_KEY=ZxqyyIPPX0oWrrOwsxXgl0hHnTx3mBVhQ2kvW1YB4MM=
124+
```
84125

85126
1. Reload `pomerium-proxy`. Navigate to httpbin (by default, `https://httpbin.corp.${YOUR-DOMAIN}.com`), and login as usual. Click **request inspection**. Select `/headers`. Click **try it out** and then **execute**. You should see something like the following.
86127

87-
![httpbin displaying jwt headers](./img/inspect-headers.png)
128+
![httpbin displaying jwt headers](./img/inspect-headers.png)
88129

89130
1. `X-Pomerium-Jwt-Assertion` is the signature value. It's less scary than it looks and basically just a compressed, json blob as described above. Navigate to [jwt.io] which provides a helpful GUI to manually verify JWT values.
90131

91132
1. Paste the value of `X-Pomerium-Jwt-Assertion` header token into the `Encoded` form. You should notice that the decoded values look much more familiar.
92133

93-
![httpbin displaying decoded jwt](./img/verifying-headers-1.png)
134+
![httpbin displaying decoded jwt](./img/verifying-headers-1.png)
94135

95136
1. Finally, we want to cryptographically verify the validity of the token. To do this, we will need the signer's public key. You can simply copy and past the output of `cat ec_public.pem`.
96137

97-
![httpbin displaying verified jwt](./img/verifying-headers-2.png)
138+
![httpbin displaying verified jwt](./img/verifying-headers-2.png)
98139

99140
**Voila!** Hopefully walking through a manual verification has helped give you a better feel for how signed JWT tokens are used as a secondary validation mechanism in pomerium.
100141

0 commit comments

Comments
 (0)