fix(oidc): Add state parameter and validate nonce against login CSRF
The OIDC implicit-flow authorize request carried no state parameter and the nonce was generated with weak Math.random() and never validated client- or server-side, enabling login CSRF / forced authentication / replay (AUTHZ-VULN-05). Generate state and nonce with the Web Crypto CSPRNG, send state in the authorize request, and validate both state and the id_token nonce claim in the callback before storing tokens. Forward the nonce to /validate- token so the backend also binds the id_token to the login flow. Fixes the forged-callback fragment attack where a planted id_token with a mismatched nonce was accepted, stored, and used by the SPA.
This commit is contained in:
@@ -106,6 +106,41 @@ def test_validate_token_invalid(mock_verify_token):
|
||||
assert not data["valid"]
|
||||
assert data["error"] == "Invalid token"
|
||||
|
||||
@patch('main.verify_token')
|
||||
def test_validate_token_passes_nonce_to_verify(mock_verify_token):
|
||||
"""The endpoint must forward the supplied nonce to verify_token (anti-replay binding)."""
|
||||
mock_verify_token.return_value = {
|
||||
"email": "rene.luria@infomaniak.com",
|
||||
"iss": "https://login.infomaniak.com",
|
||||
"aud": "test-client-id",
|
||||
"nonce": "abc123",
|
||||
}
|
||||
|
||||
response = client.post("/validate-token", json={
|
||||
"id_token": "valid.token.here",
|
||||
"nonce": "abc123"
|
||||
})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["valid"]
|
||||
# verify_token must have been called with the nonce as the second positional arg
|
||||
assert mock_verify_token.call_args.args[1] == "abc123"
|
||||
|
||||
@patch('main.verify_token')
|
||||
def test_validate_token_rejects_nonce_mismatch(mock_verify_token):
|
||||
"""A mismatched nonce (forced-auth / replay token) is rejected by the backend."""
|
||||
mock_verify_token.side_effect = ValueError("Nonce mismatch: token is not bound to this login")
|
||||
|
||||
response = client.post("/validate-token", json={
|
||||
"id_token": "forged.token.here",
|
||||
"nonce": "abc123"
|
||||
})
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert not data["valid"]
|
||||
assert "Nonce mismatch" in data["error"]
|
||||
|
||||
def test_validate_token_empty_request():
|
||||
"""Test validation with empty token."""
|
||||
response = client.post("/validate-token", json={
|
||||
|
||||
Reference in New Issue
Block a user