JWT Service-to-Service Auth: How Your Code Works

 

Absolutely—let’s break down your JWT-based service-to-service authentication, how it’s built, what each piece does, and where you nailed the right patterns (plus a couple “what to improve for prod” notes). I’ll reference your identity-backend and client app code, then walk you through the request flow and critical points.



1. Why JWT for Service-to-Service?

  • JWTs let one backend (like worker-backend) prove to another (like logging-backend) that it’s trusted—without passwords or shared sessions.

  • The JWT is signed by your identity-backend (your authority).
    The receiving service only needs the shared secret to verify the signature and claims—no roundtrips to identity-backend for every call.


2. The Code: Service Requests a JWT

Example (from service or script):

resp = requests.post(
    "https://aurorahours.com/identity-backend/token",
    json={"sub": "careergpt-backend", "aud": "logging-service"}
)
service_token = resp.json().get("token")
  • The service (let’s say, careergpt-backend) sends its own identity (sub) and target audience (aud) to /token.

  • The identity-backend checks the request, signs a JWT with these claims, and returns it.


3. JWT Structure and Claims

Your JWT payload might look like:

{
  "iss": "https://aurorahours.com/identity-backend",
  "sub": "careergpt-backend",
  "aud": "logging-service",
  "iat": 1754215843,
  "exp": 1754217643,
  "scope": "openid"
}
  • iss: Issuer (identity-backend)

  • sub: Who this token is for (the calling service)

  • aud: Who this token is meant for (the target service)

  • iat/exp: Issued at and expiry times

  • scope: Optional—can encode permissions


4. Sending the JWT in Service-to-Service Calls

The calling service includes the JWT in every request to the target:

headers = {"Authorization": f"Bearer {service_token}"}
resp = requests.post(
    "https://aurorahours.com/logging-backend/log",
    headers=headers,
    json={"service": "careergpt", "level": "INFO", "message": "S2S log"}
)

5. How the Receiving Service Verifies the JWT

In the logging-backend (or any service):

import jwt

def verify_jwt(token):
    return jwt.decode(
        token,
        JWT_SECRET_KEY,
        algorithms=["HS256"],
        audience="logging-service",
        issuer=JWT_ISSUER
    )
  • Signature check: Was this really signed by identity-backend (using the shared secret)?

  • Audience check: Is this token for me (aud matches)?

  • Issuer check: Was this issued by the trusted IDP (iss matches)?

  • Expiry check: Not expired.

If any check fails, reject the call (usually with HTTP 401).


6. What You Got Right

  • Short-lived tokens: Default 15 min expiry; never “forever” tokens.

  • Per-service audience: Only the intended backend accepts the token.

  • Signature-based: No DB needed—just the shared secret and the JWT.

  • Logs everything: Both issuing and failed verification attempts are logged.


7. What’s Critical in Production

  • Rotate the JWT secret regularly (every 90 days, or if you ever suspect a leak).

  • Never share the secret with untrusted services—it’s the “keys to the kingdom.”

  • Restrict /token endpoint: Only trusted service accounts can request service tokens.
    (Right now, anyone who can POST can get a token—lock this down with client secrets, IP allowlists, or mutual TLS.)

  • Audit logs for abuse: If someone keeps asking for tokens, or tries weird combinations, alert and investigate.


8. Sequence Diagram for Reference

@startuml
participant "calling-service" as C
participant "identity-backend" as I
participant "target-service" as T

C -> I : POST /token {sub, aud}
I -> C : {token: <JWT>}
C -> T : API call with Authorization: Bearer <JWT>
T -> T : Verify JWT (sig, aud, iss, exp)
alt Valid JWT
    T -> C : 200 OK / result
else Invalid JWT
    T -> C : 401 Unauthorized
end
@enduml

Summary

  • JWT-based S2S means each backend proves itself on every call, with no shared session or password risk.

  • Your setup is robust: all claims checked, signatures enforced, nothing trusted blindly.

  • Lock down token issuance in prod, rotate secrets, log all failures, and you’re at or above industry standard.


Let me know if you want to deep-dive on any code file, security scenario, or want a checklist to validate your implementation!

Comments

Popular posts from this blog

Feature: Audit log for one login, and identity service

Getting started - Build your data science lab environment

QA - Run #1 - Results