-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7b83a90
commit ead248b
Showing
5 changed files
with
198 additions
and
109 deletions.
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 |
---|---|---|
@@ -1,54 +1,58 @@ | ||
import { Buffer } from 'buffer' | ||
import * as Base64 from 'crypto-js/enc-base64url' | ||
import { HmacSHA256, HmacSHA384, HmacSHA512 } from 'crypto-js' | ||
import { Buffer } from 'buffer'; | ||
import * as Base64 from 'crypto-js/enc-base64url'; | ||
import { HmacSHA256, HmacSHA384, HmacSHA512 } from 'crypto-js'; | ||
|
||
const base64decode = (str) => Buffer.from(str, 'base64').toString() | ||
// Function to decode base64 strings | ||
const base64decode = (str) => Buffer.from(str, 'base64').toString(); | ||
|
||
// Hashing functions mapped to JWT algorithms | ||
const hashLibraries = { | ||
HS256: HmacSHA256, | ||
HS384: HmacSHA384, | ||
HS512: HmacSHA512, | ||
} | ||
}; | ||
|
||
export class JWT { | ||
// JWT validation process: | ||
// 1. Split the token by '.' to get the header (json), payload (json), and signature (string). | ||
// 2. Calculate a signature using the algorithm in the header (hardcoded here) to join the header and payload with a | ||
// '.', and hash it using a secret value | ||
// 3. Compare the calculated signature with the one from the token. If they match, the token is valid. If not, the | ||
// token has been tampered with. | ||
|
||
constructor(token, secret) { | ||
const [ header_base64, payload_base64, origSignature ] = token.split('.') | ||
|
||
this.header_base64 = header_base64 | ||
this.payload_base64 = payload_base64 | ||
|
||
this.header = JSON.parse(base64decode(header_base64)) | ||
this.payload = JSON.parse(base64decode(payload_base64)) | ||
|
||
this.origSignature = origSignature | ||
|
||
this.hasher = hashLibraries[this.header.alg] | ||
this.secret = secret | ||
const [header_base64, payload_base64, origSignature] = token.split('.'); | ||
|
||
this.header_base64 = header_base64; | ||
this.payload_base64 = payload_base64; | ||
|
||
try { | ||
// Decode header and payload from base64 | ||
this.header = JSON.parse(base64decode(header_base64)); | ||
this.payload = JSON.parse(base64decode(payload_base64)); | ||
} catch (e) { | ||
// Invalid payload or header, initialize empty objects | ||
this.header = {}; | ||
this.payload = {}; | ||
} | ||
|
||
this.origSignature = origSignature; | ||
this.hasher = hashLibraries[this.header.alg]; | ||
this.secret = secret; | ||
} | ||
|
||
// Validates the JWT token | ||
validate() { | ||
console.log(`validating token using ${this.header.alg} algorithm.`) | ||
const calculatedSignature = Base64.stringify( | ||
this.hasher( | ||
`${this.header_base64}.${this.payload_base64}`, | ||
this.secret | ||
) | ||
) | ||
return calculatedSignature === this.origSignature | ||
try { | ||
const calculatedSignature = Base64.stringify( | ||
this.hasher(`${this.header_base64}.${this.payload_base64}`, this.secret) | ||
); | ||
return calculatedSignature === this.origSignature; | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
|
||
// Returns the payload object | ||
payloadObject() { | ||
return this.payload | ||
return this.payload; | ||
} | ||
|
||
// Returns the algorithm used in JWT | ||
algUsed() { | ||
return this.header.alg | ||
return this.header.alg; | ||
} | ||
} | ||
} |
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 |
---|---|---|
@@ -1,21 +1,38 @@ | ||
import { JWT } from './JWT.js' | ||
import { JWT } from './JWT.js'; | ||
|
||
/** | ||
* Handle an HTTP request to validate a JWT. | ||
* | ||
* @param {Request} request - The incoming HTTP request. | ||
* @param {any} context - Context providing runtime information. | ||
* @returns {Response} HTTP response with validation result. | ||
*/ | ||
export async function handleHttpRequest(request, context) { | ||
const token = await request.text() | ||
const secret = context.environmentVars['JWT_SECRET'] || '' | ||
const resp = { | ||
valid: false | ||
} | ||
// Extract the JWT token from the request body | ||
const { token } = await request.json(); | ||
|
||
// Retrieve the secret key from environment variables | ||
const secret = context.environmentVars['JWT_SECRET'] || 'your-256-bit-secret'; | ||
|
||
// Initialize response structure | ||
const resp = { valid: false }; | ||
|
||
// Create JWT instance with the token and secret | ||
const jwt = new JWT(token, secret); | ||
|
||
// Validate the JWT | ||
const isValid = jwt.validate(); | ||
|
||
const jwt = new JWT(token, secret) | ||
const isValid = jwt.validate() | ||
// If valid, update response with additional JWT info | ||
if (isValid) { | ||
resp.valid = true | ||
resp.payload = jwt.payloadObject() | ||
resp.alg = jwt.algUsed() | ||
resp.valid = true; | ||
resp.payload = jwt.payloadObject(); // Extract payload | ||
resp.alg = jwt.algUsed(); // Extract algorithm used | ||
} | ||
|
||
// Return the response with appropriate HTTP status code | ||
return new Response(JSON.stringify(resp), { | ||
status: isValid ? 200 : 403 | ||
}) | ||
} | ||
status: isValid ? 200 : 403, // 200 OK for valid token, 403 Forbidden for invalid | ||
headers: { 'Content-Type': 'application/json' }, // Set response content type | ||
}); | ||
} |
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
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
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 |
---|---|---|
@@ -1,58 +1,126 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>JWT Validator</title> | ||
<style type="text/css"> | ||
.wrapper { | ||
max-width: 640px; | ||
margin: 1rem auto; | ||
} | ||
fieldset { | ||
margin-bottom: 1rem; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div class="wrapper"> | ||
<form action="#" id="validator"> | ||
<fieldset> | ||
<legend for="token">Your token:</legend> | ||
<textarea name="token" id="token" cols="70" rows="10"></textarea> | ||
</fieldset> | ||
<button type="submit">Validate</button> | ||
</form> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>JWT Validator</title> | ||
<style type="text/css"> | ||
body { | ||
font-family: Arial, sans-serif; | ||
background-color: #f4f4f4; | ||
color: #333; | ||
} | ||
.wrapper { | ||
max-width: 640px; | ||
margin: 2rem auto; | ||
padding: 1rem; | ||
background: white; | ||
border-radius: 8px; | ||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | ||
} | ||
fieldset { | ||
border: 1px solid #ddd; | ||
padding: 0.5rem; | ||
} | ||
textarea { | ||
width: calc(100% - 1rem); /* Adjust width to prevent overflow */ | ||
padding: 0.25rem; | ||
border: 1px solid #ddd; | ||
border-radius: 4px; | ||
margin-top: 0.5rem; | ||
box-sizing: border-box; /* Include padding and border in the element's total width and height */ | ||
} | ||
button { | ||
padding: 0.5rem 1rem; | ||
color: white; | ||
background-color: #007bff; | ||
border: none; | ||
border-radius: 4px; | ||
cursor: pointer; | ||
margin-top: 1rem; | ||
} | ||
button:hover { | ||
background-color: #0056b3; | ||
} | ||
#results { | ||
margin-top: 1rem; | ||
padding: 1rem; | ||
background-color: #eee; | ||
border-radius: 4px; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div class="wrapper"> | ||
<h1>JWT Validator</h1> | ||
<form id="validator"> | ||
<fieldset> | ||
<legend>Enter Your Token:</legend> | ||
<textarea | ||
name="token" | ||
id="token" | ||
rows="10" | ||
placeholder="Paste your JWT here..." | ||
> | ||
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJjb21tZW50IjoiRWRnZSBmdW5jdGlvbnMgYXJlIHN3ZWV0ISJ9.hc0nVeZWhE6MEf-CHJwljTY3uo6qqm8q_V_0zwm34tFALrjJDAa0CL3oUEehMRNdt3PQdcuWBgzMUqKVUWMRVQ</textarea | ||
> | ||
</fieldset> | ||
<button type="submit">Validate</button> | ||
</form> | ||
|
||
<div id="results"> | ||
<p>Click "Validate" above.</p> | ||
<p> | ||
You can generate a test token using the default signing key | ||
<code>your-256-bit-secret</code> at | ||
<a href="https://jwt.io" target="_blank" rel="noopener noreferrer" | ||
>jwt.io</a | ||
>. Valid algorithms for this example include <code>HS256</code>, | ||
<code>HS384</code>, and <code>HS512</code>. | ||
</p> | ||
|
||
<div id="results"> | ||
<p>Click "Validate" to verify your token.</p> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<script type="text/javascript"> | ||
const form = document.getElementById('validator') | ||
form.addEventListener('submit', async (e) => { | ||
e.preventDefault() | ||
const token = form.querySelector('textarea[name="token"]').value | ||
let resultString = '' | ||
try { | ||
const resp = await fetch('/jwt', { | ||
method: 'POST', | ||
body: token | ||
}) | ||
const data = await resp.json() | ||
console.log(data) | ||
if (data.valid) { | ||
resultString = `<p>Token successfully validated using ${data.alg}. Refresh the page to try another token.</p><p>Payload:</p><pre>${JSON.stringify(data.payload)}</pre>` | ||
} else { | ||
resultString = '<p>An error occurred validating the token.</p>' | ||
} | ||
} catch (e) { | ||
resultString = '<p>An error occurred validating the token.</p>' | ||
console.error(e) | ||
} finally { | ||
document.getElementById('results').innerHTML = resultString | ||
} | ||
}) | ||
</script> | ||
</body> | ||
</html> | ||
<script type="text/javascript"> | ||
document | ||
.getElementById('validator') | ||
.addEventListener('submit', async (e) => { | ||
e.preventDefault(); | ||
const token = document.getElementById('token').value; | ||
let resultString = ''; | ||
|
||
try { | ||
const response = await fetch('/jwt', { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify({ token }), | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error(`Server error occurred. Response code: ${response.status}, Response body: ${JSON.stringify(await response.json())}`); | ||
} | ||
|
||
const data = await response.json(); | ||
|
||
if (data.valid) { | ||
resultString = `<p>Token successfully validated using ${ | ||
data.alg | ||
}.</p><p>Payload:</p><pre>${JSON.stringify( | ||
data.payload, | ||
null, | ||
2 | ||
)}</pre>`; | ||
} else { | ||
resultString = '<p>An error occurred validating the token.</p>'; | ||
} | ||
} catch (error) { | ||
resultString = `<p>${error.message}</p>`; | ||
console.error('Error:', error); | ||
} finally { | ||
document.getElementById('results').innerHTML = resultString; | ||
} | ||
}); | ||
</script> | ||
</body> | ||
</html> |