-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JWT Signing #212
Merged
Merged
JWT Signing #212
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
3fef9d1
Adding initial commit of Edgeworker example to sign JWTs
nighthauk 019a6d5
Updating to include Akamai copyright notice as well as modify README,…
nighthauk a8b1df4
Correcting copyright year in one place.
nighthauk 527e6cf
Adjusting readme per PR reviewer comments.
nighthauk 5eb1f92
Addine further instruction and examples for generating keys for use, …
nighthauk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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." | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; | ||
evan-hughes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great explanation! Thanks!