Skip to content

Commit

Permalink
Merge pull request #212 from nighthauk/feature/jwt-signing-example
Browse files Browse the repository at this point in the history
JWT Signing
  • Loading branch information
lperra authored Nov 1, 2024
2 parents b087782 + 5eb1f92 commit 9beed9a
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 0 deletions.
84 changes: 84 additions & 0 deletions edgecompute/examples/authentication/jwt-signing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# JWT Signing

_Keyword(s):_ jwt, authentication<br>

## Copyright Notice

(c) Copyright 2024 Akamai Technologies, Inc. Licensed under Apache 2 license.

This project provides an Akamai Edgeworker solution for signing JSON Web Tokens (JWTs) and attaching them to API requests. It includes an example of how to sign a JWT using HMAC-SHA256 and then add the JWT and an API key to the headers of an outgoing request.

## Security Considerations

> [!IMPORTANT]
> Secrets are stored in [Property Manager variables](https://techdocs.akamai.com/property-mgr/docs/user-defined-vars). Property Manager variables should be marked as hidden to prevent exposure with akamai-x-get-extracted-values. Depending on your security considerations, you may wish to store secrets elsewhere or use asymmetric keys.
## Usage Example

> [!NOTE]
> This EdgeWorker requires you to create a Property Manager variable named `PMUSER_JWT_HMAC_KEY` (if you prefer something different, ensure that the reference in the Edgeworker is updated), set it to hidden, and then paste an encoded key into it.
If you do not yet have an HMAC key, an example for doing so can be done by running the following in your browser:

```
const key = await crypto.subtle.generateKey(
{
name: "HMAC",
hash: { name: "SHA-256" },
},
true, // Key must be extractable to export
["sign", "verify"]
);
// To export the key in "raw" format
const rawKey = await crypto.subtle.exportKey("raw", key);
console.log("Raw HMAC key:", new Uint8Array(rawKey));
// To export the key in "jwk" format
const jwkKey = await crypto.subtle.exportKey("jwk", key);
console.log("JWK HMAC key:", JSON.stringify(jwkKey));
```

### Explanation

1. **Raw Key**:

- `raw` format returns the binary representation of the key.
- The `Uint8Array` wrapper helps display the raw key as an array of bytes, which is useful for debugging.
- Console Output for `raw` key:

```
Raw HMAC key: Uint8Array(32) [24, 134, 239, 140, ...] // Array of bytes
```

2. **JWK Key**:

- `jwk` format returns a JSON Web Key, a JSON object that includes key details.
- This format is more readable and interoperable, especially when working with web APIs.
- Console Output for `jwk` key:

```
JWK HMAC key: {
"kty": "oct",
"k": "SGVsbG9Xb3JsZEtleQ...", // Base64 URL encoded key
"alg": "HS256",
"ext": true
}
```

In the JWK output:

- `kty`: Key type, "oct" for symmetric keys.
- `k`: The actual key material, base64url encoded.
- `alg`: Algorithm, here `"HS256"` for HMAC-SHA-256.
- `ext`: Indicates if the key is extractable (`true` in this case).

Take note that the main.js version uses `raw` key format. If using `jwk` in the example above, the stringified console output of `jwkKey` above may be pasted directly into the Property Manager variable `PMUSER_JWT_HMAC_KEY`.

## Resources

For more information on JWT Module, please refer to the following resources:

- [JWT API Documentation](https://techdocs.akamai.com/edgeworkers/docs/jwt)
- [Crypto module documentation](https://techdocs.akamai.com/edgeworkers/docs/crypto)
- See the repo [README](https://github.com/akamai/edgeworkers-examples#Resources) for additional guidance.
4 changes: 4 additions & 0 deletions edgecompute/examples/authentication/jwt-signing/bundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"edgeworker-version": "0.1",
"description": "Generates a signed JWT for origin verification."
}
88 changes: 88 additions & 0 deletions edgecompute/examples/authentication/jwt-signing/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
(c) Copyright 2024 Akamai Technologies, Inc. Licensed under Apache 2 license.
Version: 1.0
Purpose: EdgeWorker that signs a JWT for origin validation by leveraging the Web Crypto API
*/

import { logger } from 'log';
import { crypto } from 'crypto';
import { TextEncoder, base64url } from 'encoding';

async function signJWT(payload, secret) {
logger.log('Start: Signing JWT');

try {

// Get current time for issued claim
const currentTime = Math.floor(Date.now() / 1000);

// Set payload 'issued at' (iat) and 'expiration' (exp) claims
payload.iat = currentTime;
payload.exp = currentTime + 300; // Expires in 5 minutes

// JWT header
const header = {
alg: 'HS256'
, typ: 'JWT'
};

// Encode header and payload in base64URL for JWT
const encoder = new TextEncoder();
const encodedHeader = base64url.encode(encoder.encode(JSON.stringify(header)));
const encodedPayload = base64url.encode(encoder.encode(JSON.stringify(payload)));
const message = `${encodedHeader}.${encodedPayload}`;

// Import the secret key for HMAC signing
const keyData = new Uint8Array(encoder.encode(secret));
const cryptoKey = await crypto.subtle.importKey(
'raw'
, keyData.buffer
, { name: 'HMAC', hash: 'SHA-256' }
, false
, ['sign']
);

// Sign the JWT
const signature = await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(message));
const encodedSignature = base64url.encode(new Uint8Array(signature));

// Return the complete JWT
return `${message}.${encodedSignature}`;

} catch (error) {
logger.error('Error during JWT signing', error);

return error.message;
}
}

export async function onOriginRequest(request) {
logger.log('Start: onOriginRequest');

try {
// Retrieve secrets from environment
const secretKey = request.getVariable('PMUSER_JWT_HMAC_KEY');
const apiKey = request.getVariable('PMUSER_CSS_API_KEY');

// Prepare JWT payload
const payload = {
sub: apiKey
, iss: 'issuer-string-here'
};

// Generate the JWT
const jwt = await signJWT(payload, secretKey);

// Modify outbound request headers
request.removeHeader('Transfer-Encoding');
request.addHeader('X-API-KEY', apiKey);
request.addHeader('X-JWT', `Bearer ${jwt}`);

} catch (error) {
logger.error('Error in onOriginRequest', error);

throw error;
}
}

0 comments on commit 9beed9a

Please sign in to comment.