From 6145b8dc0bf05e904adc79fe2d9d929038cc00c7 Mon Sep 17 00:00:00 2001 From: Emin Martinian Date: Thu, 15 May 2025 21:10:51 -0400 Subject: [PATCH 1/2] fixed minor typo --- docs/index.html | 4794 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4794 insertions(+) create mode 100644 docs/index.html diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..b3bc1a3 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,4794 @@ + + + + +Tips, Tricks, and Reasons for JSON Web Tokens (JWTs) + + + + + + + + + + + +
+
+

Tips, Tricks, and Reasons for JSON Web Tokens (JWTs)

+

Emin Martinian

+
+ +
+
+

JWT: JSON Web Token

+

+Used for authentication/authorization such as: +

+ + + + + + +
+
+
+
+

Why JWTs?

+

+Imagine app with many features + servers + engineers: +

+ +
    +
  • Load balance, payments, profiles, PII, DB
  • +
  • Local/remote/international workers + consultants
  • +
  • How to manage security? +
      +
    • Can't give everyone access to sensitive info
    • + +
  • + +
+
+
+
+
+

Authentication vs Validation

+ + + +
+

+

+
+
+
+
+
+

JWT: Authentication Request

+ + + + +
+

+

+
+
+
+
+
+

JWT: Authentication Response

+ +
+

+

+
+
+
+
+
+

JWT: Application Request

+ + + +
+

+

+
+
+
+
+
+

Separate Auth From Validation

+

+Auth Server has secrets; needs security + maintenance +

+ +
    +
  • App Server(s) needs public keys; low security
  • +
  • Easy to deploy App Server(s); e.g., serverless
  • +
  • Lower security for App Server(s), logs, debug, etc.
  • + +
+
+
+
+
+

What do JWTs look like?

+

+Base64 encoded header.payload.signature: +

+ +
+ +
HEADER:     { "alg": "EdDSA", "typ": "JWT" }
+
+
+ +
+ +
PAYLOAD:    {"sub": "a", "name": "arbitrary data", "iat": 1 }
+
+
+ +
+ +
SIGNATURE:  SU6aXJ0YbH7Vg1jROpQfvnhn98Rt9zBeS7-c5O9jH-L
+            L5mQqMMFq61eZjf0tLLqExm-dckRUNa3-qT7R2SKmCw
+            
+
+
+ +
+ +
ENCODED JWT:   eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9
+               .eyJzdWIiOiJhIiwibmFtZSI6ImIiLCJpYXQiOjF9
+               .SU6aXJ0YbH7Vg1jROpQfvnhn98Rt9zBeS7-c5O9jH-L
+                L5mQqMMFq61eZjf0tLLqExm-dckRUNa3-qT7R2SKmCw
+
+
+ + +

+Signed using EdDSA with secret key: +

+ +
+ +
MC4CAQAwBQYDK2VwBCIEIC+D6rD2YbXtV0ccR3smoR0ynhVuyyqvplFLbQWDdAtn
+
+
+ + + +
+
+
+
+

Main JWT Fields

+
    +
  • sub: Subject (username, email, etc.)
  • +
  • iat: Issued at (useful for checking freshness)
  • +
  • exp: Expiry (useful for managing life-cycle )
  • +
  • nbf: Not before (useful for managing life-cycle )
  • + +
+
+
+
+
+

Python/Flask Example

+
    +
  • Easy to verify/decode using libraries (e.g., pyjwt) +
      +
    • can compose checks using decorators:
    • + +
  • + +
+ +
+ +
@app.route('/support/urgent')  # built-in flask decorator
+@requires_jwt                  # custom decorator to validate JWT
+@jwt_claims(['paid_support'])  # ensures token is for premium user
+@jwt_iat(datetime.timedelta(hours=24))  # ensure recent token
+def support_urgent():
+    ... # process ending support request
+
+
+
+
+
+
+

Python/Flask Example

+
    +
  • Easy to verify/decode using libraries (e.g., pyjwt) +
      +
    • can compose checks using decorators:
    • + +
  • + +
+ + + +
+ +
@app.route('/support/urgent')  # built-in flask decorator
+@requires_jwt                  # custom decorator to validate JWT
+@jwt_claims(['paid_support'])  # ensures token is for premium user
+@jwt_iat(datetime.timedelta(hours=24))  # ensure recent token
+def support_urgent():
+    ... # process ending support request
+
+
+
+
+
+
+

Python/Flask Example

+
    +
  • Easy to verify/decode using libraries (e.g., pyjwt) +
      +
    • can compose checks using decorators:
    • + +
  • + +
+ + + +
+ +
@app.route('/support/urgent')  # built-in flask decorator
+@requires_jwt                  # custom decorator to validate JWT
+@jwt_claims(['paid_support'])  # ensures token is for premium user
+@jwt_iat(datetime.timedelta(hours=24))  # ensure recent token
+def support_urgent():
+    ... # process ending support request
+
+
+
+
+
+
+

Python/Flask Example

+
    +
  • Easy to verify/decode using libraries (e.g., pyjwt) +
      +
    • can compose checks using decorators:
    • + +
  • + +
+ + + +
+ +
@app.route('/support/urgent')  # built-in flask decorator
+@requires_jwt                  # custom decorator to validate JWT
+@jwt_claims(['paid_support'])  # ensures token is for premium user
+@jwt_iat(datetime.timedelta(hours=24))  # ensure recent token
+def support_urgent():
+    ... # process ending support request
+
+
+
+
+
+
+

Python/Flask Example

+
    +
  • Easy to verify/decode using libraries (e.g., pyjwt) +
      +
    • can compose checks using decorators:
    • + +
  • + +
+ + + +
+ +
@app.route('/support/urgent')  # built-in flask decorator
+@requires_jwt                  # custom decorator to validate JWT
+@jwt_claims(['paid_support'])  # ensures token is for premium user
+@jwt_iat(datetime.timedelta(hours=24))  # ensure recent token
+def support_urgent():
+    ... # process ending support request
+
+
+
+
+
+
+

Example of @requires_jwt

+
+ +
def requires_jwt(func):
+    @wraps(func)
+    def decorated(*args, **kwargs):        
+        token = request.headers.get("Authorization").split(" ")[1]
+        if not token:
+            return 'missing token', 401  # if no token return error   
+        try:
+            g.decoded_jwt = jwt.decode(
+                token, algorithms=['EdDSA'],
+                key=current_app.config['JWT_KEY'])  # public key
+            return func(*args, **kwargs)
+        except Exception as problem:
+            return f'{problem=}', 401 # return 401 or other error code
+    return decorated
+
+
+
+
+
+
+

Example of @jwt_claims

+
+ +
def jwt_claims(claims_list: typing.Sequence[str]):
+    def make_decorator(func):
+        @wraps(func)
+        def decorated(*args, **kwargs):        
+            missing = [c for c in claims_list
+                       if not g.decoded_jwt.get(c)]
+            if missing:
+                return f'Missing claims: {missing}', 401
+            return func(*args, **kwargs)
+        return decorated
+    return make_decorator
+
+
+
+
+
+
+

Example Use Case: Proxy

+
    +
  • Auth Server grants JWT letting Alice to act for Bob
  • +
  • claims: {"sub": "Alice", "proxy": "Bob"}
  • +
  • Alice sends request combining to act for Bob
  • + +
+
+
+
+
+

Example Use Case: Proxy

+
    +
  • Auth Server grants JWT letting Alice to act for Bob
  • +
  • claims: {"sub": "Alice", "proxy": "Bob"}
  • +
  • Alice sends request combining to act for Bob
  • + +
+ +
+ +
@APP.route("/issue")
+@requires_jwt
+def issue():
+    "Example route to create an issue."
+    user = g.decoded_jwt.get('proxy', g.decoded_jwt.get('sub'))
+    msg = f'Created issue assigned to {user}.'
+    # ... Create the actual issue here
+
+
+
+    return msg
+
+
+
+
+
+
+

Example Use Case: Proxy

+
    +
  • Auth Server grants JWT letting Alice to act for Bob
  • +
  • claims: {"sub": "Alice", "proxy": "Bob"}
  • +
  • Alice sends request combining to act for Bob
  • + +
+ +
+ +
@APP.route("/issue")
+@requires_jwt
+def issue():
+    "Example route to create an issue."
+    user = g.decoded_jwt.get('proxy', g.decoded_jwt.get('sub'))
+    msg = f'Created issue assigned to {user}.'
+    # ... Create the actual issue here
+    real_user = g.decoded_jwt['sub']
+    if real_user != user:
+        msg += f'\n{real_user} acted on behalf of {user}'
+    return msg
+
+
+
+
+
+
+

Caveats

+
    +
  • Beware using header fields to check signature +
      +
    • don't trust alg field or limit possibilities +
        +
      • e.g., algorithms=['EdDSA']
      • + +
    • +
    • be careful with kid, jku, jwk, etc.
    • + +
  • +
  • Don't simulate sessions with JWTs +
      +
    • Use access/refresh tokens to solve logout/revocation
    • + +
  • + +
+
+
+
+
+

Example JKU Header Attack

+
    +
  • Header can provide URL for key (useful): +
      +
    • {alg: "EdDSA", jku: "https://good.com/pk.json"}
    • + +
  • +
  • Attacker can replace JKU with their own key: +
      +
    • {alg: "EdDSA", jku: "https://bad.com/pk.json"}
    • + +
  • +
  • Don't trust header (validate against whitelist)
  • + +
+
+
+
+
+

Example ALG Header Attack

+
    +
  • Header can provide URL for key (useful): +
      +
    • {alg: "EdDSA", jku: "https://good.com/pk.json"}
    • + +
  • +
  • Attacker can replace ALG with symmetric version: +
      +
    • {alg: "HS256", jku: "https://good.com/pk.json"}
    • + +
  • +
  • Don't trust header (validate against whitelist)
  • + +
+
+
+
+
+

Revocation via Access/Refresh

+
    +
  • Problem: Can't cancel or logout a JWT
  • +
  • Solution: Refresh/Access token +
      +
    • "refresh token" with long expiry
    • +
    • used to get access token w/o credential check
    • +
    • "access token" with short expiry
    • +
    • can be used to access services
    • + +
  • + +
+ + + +
+
+
+
+

Get Refresh Token

+ +
+

+

+
+
+
+
+
+

Get Access Token

+ +
+

+

+
+
+
+
+
+

Use Access Token

+ +
+

+

+
+
+
+
+
+

Revocation

+ +
+

+

+
+
+
+
+
+

Separate validation from parsing

+ + +
    +
  • Can use middleware to verify signature
  • +
  • +e.g., NGINX can verify before passing to app server +

    + +
    +

    +

    +
  • +
  • See example.conf in nginx directory on github.com/aocks/ox_jwt
  • + +
+
+
+
+
+

Summary and next steps

+ + + +
+
+
+
+ + + + + + + + From 398d6a6c3e5fe7a7f861d0871b44001428b32464 Mon Sep 17 00:00:00 2001 From: Emin Martinian Date: Thu, 15 May 2025 21:11:02 -0400 Subject: [PATCH 2/2] fixed minor typo --- docs/slides.html | 2 +- docs/slides.org | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/slides.html b/docs/slides.html index 90fe097..b3bc1a3 100644 --- a/docs/slides.html +++ b/docs/slides.html @@ -2794,7 +2794,7 @@

Example ALG Header Attack

  • Attacker can replace ALG with symmetric version:
      -
    • {alg: "HS256", jku: "https://bad.com/pk.json"}
    • +
    • {alg: "HS256", jku: "https://good.com/pk.json"}
  • Don't trust header (validate against whitelist)
  • diff --git a/docs/slides.org b/docs/slides.org index 32ac3c6..90feb66 100644 --- a/docs/slides.org +++ b/docs/slides.org @@ -831,7 +831,7 @@ print(req.text) - Header can provide URL for key (useful): - src_shell[:exports code]{{alg: "EdDSA", jku: "https://good.com/pk.json"}} - Attacker can replace ALG with symmetric version: - - src_shell[:exports code]{{alg: "HS256", jku: "https://bad.com/pk.json"}} + - src_shell[:exports code]{{alg: "HS256", jku: "https://good.com/pk.json"}} - Don't trust header (validate against whitelist)