OAuth2 Authorization Code Flow (with OIDC): How Your Code Works
Absolutely! Let’s review the OAuth2 Authorization Code Flow as implemented in your identity-backend and Echo client app, step by step, with code context and real-world best practices.
OAuth2 Authorization Code Flow (with OIDC): How Your Code Works
1. What Is It? (And Why Use It?)
- OAuth2 Authorization Code Flow (with OpenID Connect) is the industry gold standard for user SSO.
- It securely lets your client app (Echo API Gateway) authenticate real users without ever seeing their password—identity-backend handles the sensitive bits.
- Result: You get a signed JWT as proof of login, and your app can trust it.
2. Step-by-Step Flow (Echo-Style)
Step 1: User Attempts to Access a Protected Resource
- User browses to http://localhost:5000.
- Echo client app checks: “Do I have a valid JWT for this session?”
- No JWT: Redirect to /login
- Yes: Show the app
Code:
@app.route("/")
def home():
if not session.get("id_token"):
return redirect(url_for("login"))
return render_template("index.html")
Step 2: Echo App Redirects User to Identity-Backend /authorize
- Client app generates a random state (for CSRF defense), sets OIDC params.
- Redirects to:
/identity-backend/authorize?client_id=browser-ui&redirect_uri=...&response_type=code&scope=openid&state=...
Code:
@app.route("/login")
def login():
state = str(uuid.uuid4())
session["state"] = state
params = {
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"response_type": "code",
"scope": "openid",
"state": state
}
return redirect(f"{IDP_URL}/authorize?{urlencode(params)}")
Step 3: User Logs in on Identity-Backend
- Identity-backend shows login form at /authorize.
- User submits username/password to /login.
- If credentials are valid:
- Generate a single-use authorization code.
- Store it in authcodes.db.
- Redirects user back to client app:
/callback?code=<auth_code>&state=<original_state>
Code:
@app.route("/authorize")
def authorize():
# ... parameter validation
session["client_id"] = client_id
# Show login form
@app.route("/login", methods=["POST"])
def handle_login():
# ...validate credentials
auth_code = str(uuid.uuid4())
with sqlite3.connect(AUTH_CODE_DB) as conn:
conn.execute("INSERT INTO codes (code, username, ...) VALUES (?, ...)", (auth_code, ...))
# Redirect with ?code=...&state=...
Step 4: Client App Handles Callback, Verifies State, Exchanges Code for JWT
- Client app verifies that state matches what was originally sent.
- POSTs to /token with:
- code, client_id, client_secret, redirect_uri
- If code is valid, identity-backend returns:
{ "id_token": "<jwt>", "token_type": "Bearer", ... }
Code:
@app.route("/callback")
def callback():
code = request.args.get("code")
state = request.args.get("state")
if state != session["state"]:
abort(400, "State mismatch")
token_resp = requests.post(
f"{IDP_URL}/token",
data={
"code": code,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"redirect_uri": REDIRECT_URI
}
)
id_token = token_resp.json()["id_token"]
session["id_token"] = id_token
return redirect(url_for("home"))
Step 5: User is Authenticated—JWT is Used for All Future Requests
- JWT (id_token) is stored in user’s session (not in localStorage!).
- Every protected request checks this JWT for validity/expiration.
3. Security Details: How You Got it Right
- State param checked—prevents CSRF attacks.
- Single-use auth codes—never reused; deleted from DB after token exchange.
- Short-lived tokens—JWT expires quickly (15 min default).
- Signature, audience, issuer all checked—no “just decode and trust.”
- No credentials go to the client app—all handled by identity-backend.
- All failures and suspicious events are logged.
4. What Could Be Even Stronger
- Enforce HTTPS everywhere. (Don’t even allow HTTP except in dev.)
- Consider PKCE for public clients.
- Multi-factor can be plugged into the login step.
- Explicit session expiry/rotation on logout or login from new device.
5. Sequence Diagram (PlantUML)
@startuml
actor User as U
participant "Browser (Client App)\n(API Gateway)" as C
participant "Identity Service\n(identity-backend)" as I
U -> C : GET /
C -> C : Check session for JWT
alt No valid JWT
C -> I : GET /authorize?client_id=...&redirect_uri=...&state=...
I -> U : Show login form
U -> I : POST /login
I -> I : Validate, issue code, redirect
I -> C : Redirect /callback?code=...&state=...
C -> I : POST /token {code, ...}
I -> C : {id_token (JWT)}
C -> C : Store JWT, login session valid
end
U -> C : Use app as logged-in user
@enduml
Summary
- OAuth2 Auth Code Flow (with OIDC) is how your user SSO works, end-to-end.
- No passwords or codes ever leak to the client app.
- Your code gets it right: state checked, codes single-use, JWTs signed and validated.
- Add PKCE, MFA, and strict secret handling for enterprise, and you’re audit-proof.
Let me know if you want to walk through the code line-by-line or see how to plug in advanced features like PKCE or federated login!
Comments
Post a Comment