What is JWT

Popcorn hack: list 3 real world applications of JWT:

Google Authenticator, Emails, Banks, Amazon shopping.

Why do you need JWT

JSON Web Tokens (JWT) are crucial for secure and efficient user authentication in web development.

Components of JWT

  1. Header
  2. Payload
  3. Signature

Deep Dive into Anatomy of JWT

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);

Putting It Together

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==

JWT with Spring Boot

Big Idea

JWTProcess

  1. JwtAuthFilter is executed first (validates and checks JWT Tokens)
  2. Validate JWT Process

Here is a diagram that makes it easier to understand the process tha happens between JWT and the API calls. JWTApi

Popcorn Hack: Create your own diagram to help visualize the JWT process

JWT Walkthrough

Logic flow

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):

Storing JWT

There are a few different options for storing a JWT in a JavaScript application:

  1. 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.

  2. 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.

  3. 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).

Postman

  1. Trying to access /api/person/ without jwt token stored in Cookies
  2. Trying to access /api/person/ with a wrong jwt token stored in Cookies
  3. Trying to access /api/person/ with a jwt token that does not have the signature
  4. Accessing /api/person/ with generated jwt token stored in Cookies header

Implementation Approaches and Security Considerations

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

Hacks

  1. Implementation

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.

  1. MC Knowledge test (5)