Popcorn hack: list 3 real world applications of JWT:
Google Authenticator, Emails, Banks, Amazon shopping.
JSON Web Tokens (JWT) are crucial for secure and efficient user authentication in web development.
Encoded: Json Web Token (what you send to and from the client) Decoded: algorithm, data, verify token hasn’t been changed
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
// header
{
"alg": "HS256", //type of sign in algorithm used for encoding and decoding
"typ": "JWT" //type of token
}
// payload
{
"sub": "123", //example of a registered claim
"name": "jwt lesson",
"iat": 1516239022",
"authorities": [
"ADMIN",
"MANAGER"
],
"extra-claims": "some data here"
}
//signature
{
HMACSHA256(
base64UrlEncode(header) + "." + // header
base64UrlEncode(payload), // payload
your-256-bit-secret
) secret base64 encoded
}
Header
import java.util.Base64;
public class JwtHeaderExample {
public static void main(String[] args) {
// JWT Header
String algorithm = "HS256";
String type = "JWT";
// Combine Header properties
String header = "{\"alg\":\"" + algorithm + "\",\"typ\":\"" + type + "\"}";
// Base64 URL encode the header
String base64UrlHeader = Base64.getUrlEncoder().encodeToString(header.getBytes());
System.out.println("JWT Header: " + base64UrlHeader);
}
}
// Run this code
JwtHeaderExample.main(null);
JWT Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class JwtPayloadExample {
public static void main(String[] args) {
// JWT Payload (Claims)
Map<String, Object> claims = new HashMap<>();
claims.put("name", "Grace");
claims.put("class", "CSA");
claims.put("exp", System.currentTimeMillis() + 1800000); // 30 minutes expiration
// Convert Claims to JSON and Base64 URL encode the payload
String payload = mapToJson(claims); // payload
String base64UrlPayload = Base64.getUrlEncoder().encodeToString(payload.getBytes());
System.out.println("JWT Payload: " + base64UrlPayload);
}
private static String mapToJson(Map<String, Object> map) {
// Implement JSON conversion logic (use your preferred approach)
return "{ \"customKey\": \"customValue\" }";
}
}
// Run This Code
JwtPayloadExample.main(null);
JWT Payload: eyAiY3VzdG9tS2V5IjogImN1c3RvbVZhbHVlIiB9
Signature
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class JwtSignatureExample {
public static void main(String[] args) {
// Secret key for encoding and decoding
String secretKey = "your secret key";
// Combine Header and Payload with a period '.'
String base64UrlHeader = "base64UrlHeader"; // Placeholder for the actual base64UrlHeader
String base64UrlPayload = "base64UrlPayload"; // Placeholder for the actual base64UrlPayload
String headerPayload = base64UrlHeader + "." + base64UrlPayload;
// Generate HMAC SHA-256 signature
byte[] signature = HmacSha256(headerPayload, secretKey); // signature
// Base64 URL encode the signature
String base64UrlSignature = Base64.getUrlEncoder().encodeToString(signature);
System.out.println("JWT Signature: " + base64UrlSignature);
}
private static byte[] HmacSha256(String data, String key) {
try {
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256Hmac.init(secretKey);
return sha256Hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Error while generating HMAC SHA-256", e);
}
}
}
// Run
JwtSignatureExample.main(null);
Popcorn hack: write some comments that identify each part of the JWT
etc
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class JwtGenerator {
public static void main(String[] args) {
String secretKey = "yourSecretKey"; // secret key
String subject = "userId123";
long expirationTimeMillis = System.currentTimeMillis() + 3600000;
String jwt = buildJwt(secretKey, subject, expirationTimeMillis);
System.out.println("Generated JWT: " + jwt);
}
private static String buildJwt(String secretKey, String subject, long expirationTimeMillis) {
String header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; // header
String payload = "{\"sub\":\"" + subject + "\",\"iat\":" + System.currentTimeMillis() / 1000 +
",\"exp\":" + expirationTimeMillis / 1000 + "}"; // payload
String encodedHeader = base64UrlEncode(header);
String encodedPayload = base64UrlEncode(payload);
String dataToSign = encodedHeader + "." + encodedPayload;
String signature = signData(dataToSign, secretKey); // signature
return dataToSign + "." + signature;
}
private static String base64UrlEncode(String input) {
return Base64.getUrlEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_8));
}
private static String signData(String data, String secretKey) {
try {
Mac sha256Hmac = Mac.getInstance("HmacSHA256"); // algorithm
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256Hmac.init(secretKeySpec);
byte[] signature = sha256Hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return base64UrlEncode(new String(signature, StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("Error signing JWT", e);
}
}
}
JwtGenerator.main(null);
Generated JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQxMjMiLCJpYXQiOjE3MDMwMDE3OTIsImV4cCI6MTcwMzAwNTM5Mn0=.24Tvv71877-9AQjvv71I77-977-977-977-9WO-_ve-_ve-_ve-_vQXvv73vv71bGe-_ve-_ve-_vSHbue-_vQ==
Here is a diagram that makes it easier to understand the process tha happens between JWT and the API calls.
Popcorn Hack: Create your own diagram to help visualize the JWT process
Step 1 (Client - Login Request): The client sends a login request with user credentials (username and password) to the /authenticate endpoint.
Step 2 (JwtApiController):
Step 3 (Client - Subsequent Requests):
Step 4 (JwtRequestFilter):
Step 5 (Spring Security):
Step 6 (Error Handling - JwtAuthenticationEntryPoint):
There are a few different options for storing a JWT in a JavaScript application:
Cookies: You can store the JWT in a cookie and send it back to the server with each request. This is a simple and widely-supported option, but it has some limitations. For example, you can’t access cookies from JavaScript on a different domain, and some users may have cookies disabled in their browser settings.
Local storage: You can store the JWT in the browser’s local storage (localStorage) or session storage (sessionStorage). This option allows you to access the JWT from JavaScript on the same domain, but it is vulnerable to cross-site scripting (XSS) attacks, where an attacker can inject malicious code into your application and steal the JWT from the storage.
HttpOnly cookie: You can store the JWT in an HttpOnly cookie, which is a cookie that can only be accessed by the server and not by client-side JavaScript. This option provides some protection against XSS attacks, but it is still vulnerable to other types of attacks, such as cross-site request forgery (CSRF).
JWTs are signed to ensure they cannot be modified in transit. Signature is a crucial aspect of JWT security.
Key Usage
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.Claims;
public class TokenReceptionExample {
// Simulate receiving a token from the client
private static String receiveTokenFromClient() {
// In a real scenario, this would be received from the client (e.g., from a request header)
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiAiMTIzIiwgImV4cCI6IDE2MjM5NzYzODF9.4X1lC5fU4dV1n9l02LZyGQSy5K-O5fnZM0t6eO-w2Qs";
}
public static void main(String[] args) {
// Example of Token Reception
String receivedToken = receiveTokenFromClient();
System.out.println("Received Token: " + receivedToken);
}
}
Symmetric vs. Asymmetric Key Approaches
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
public class SymmetricKeyValidationExample {
// Symmetric Key for Token Signing and Validation
private static Key symmetricKey = Keys.secretKeyFor(io.jsonwebtoken.SignatureAlgorithm.HS256);
// Token Validation with Symmetric Key
private static Claims validateTokenSymmetric(String token) {
try {
// Parse and verify the token using the symmetric key and the HS256 algorithm
Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(symmetricKey).build().parseClaimsJws(token);
return claimsJws.getBody();
} catch (ExpiredJwtException e) {
// Handle case where the token has expired
System.out.println("Token has expired");
} catch (MalformedJwtException e) {
// Handle case where the token is invalid (e.g., tampered with)
System.out.println("Invalid token");
}
return null;
}
public static void main(String[] args) {
// Example of Token Validation with Symmetric Key
String receivedToken = receiveTokenFromClient();
Claims validatedClaims = validateTokenSymmetric(receivedToken);
if (validatedClaims != null) {
System.out.println("Validated Payload: " + validatedClaims);
}
}
}
Additional Security Considerations
I was not 100% sure what to do here, as I didn’t know how I could obtain the JWT key/cookie via postman. I tried doing this for the jokes API and using the cookies content-type, but that unfortunately did not work. What I instead ended up doing was just displaying the data from a GET request to the server, as that ended up displaying the data in a way that was similar to the format of the anatomy of JWT.