Skip to content

Commit 76ee6a5

Browse files
committed
fix(jwt): add default claims validation for iss, sub and aud
1 parent 54d6c3f commit 76ee6a5

2 files changed

Lines changed: 86 additions & 15 deletions

File tree

‎src/joserfc/_rfc7519/claims.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,49 @@ def now(self) -> int:
120120
return self._now()
121121
return self._now
122122

123+
def validate_iss(self, value: str) -> None:
124+
"""The "iss" (issuer) claim identifies the principal that issued the
125+
JWT. The processing of this claim is generally application specific.
126+
The "iss" value is a case-sensitive string containing a StringOrURI
127+
value. Use of this claim is OPTIONAL.
128+
"""
129+
if not isinstance(value, str):
130+
raise InvalidClaimError("str", "Claim 'str' must be a StringOrURI value")
131+
self.check_value("iss", value)
132+
133+
def validate_sub(self, value: str) -> None:
134+
"""The "sub" (subject) claim identifies the principal that is the
135+
subject of the JWT. The claims in a JWT are normally statements
136+
about the subject. The subject value MUST either be scoped to be
137+
locally unique in the context of the issuer or be globally unique.
138+
The processing of this claim is generally application specific. The
139+
"sub" value is a case-sensitive string containing a StringOrURI
140+
value. Use of this claim is OPTIONAL.
141+
"""
142+
if not isinstance(value, str):
143+
raise InvalidClaimError("sub", "Claim 'sub' must be a StringOrURI value")
144+
self.check_value("sub", value)
145+
146+
def validate_aud(self, value: str | list[str]) -> None:
147+
"""The "aud" (audience) claim identifies the recipients that the JWT is
148+
intended for. Each principal intended to process the JWT MUST
149+
identify itself with a value in the audience claim. If the principal
150+
processing the claim does not identify itself with a value in the
151+
"aud" claim when this claim is present, then the JWT MUST be
152+
rejected. In the general case, the "aud" value is an array of case-
153+
sensitive strings, each containing a StringOrURI value. In the
154+
special case when the JWT has one audience, the "aud" value MAY be a
155+
single case-sensitive string containing a StringOrURI value. The
156+
interpretation of audience values is generally application specific.
157+
Use of this claim is OPTIONAL.
158+
"""
159+
if isinstance(value, str) or _validate_list_of_strings(value):
160+
self.check_value("aud", value)
161+
else:
162+
raise InvalidClaimError(
163+
"aud", "Claim 'aud' must be an array of StringOrURI value or a single StringOrURI value"
164+
)
165+
123166
def validate_exp(self, value: int) -> None:
124167
"""The "exp" (expiration time) claim identifies the expiration time on
125168
or after which the JWT MUST NOT be accepted for processing. The
@@ -167,5 +210,9 @@ def _validate_numeric_time(s: int) -> bool:
167210
return isinstance(s, (int, float))
168211

169212

213+
def _validate_list_of_strings(s: list[str]) -> bool:
214+
return isinstance(s, list) and all(isinstance(v, str) for v in s)
215+
216+
170217
def _generate_now() -> int:
171218
return int(time.time())

‎tests/jwt/test_claims.py‎

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,30 @@ def test_essential_claims(self):
4848

4949
claims_requests.validate({"sub": "a", "iss": "a", "name": "joserfc"})
5050

51+
def test_claim_iss(self):
52+
claims_requests = jwt.JWTClaimsRegistry(iss={"essential": True})
53+
claims_requests.validate({"iss": "a"})
54+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"iss": 1})
55+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"iss": True})
56+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"iss": ["a"]})
57+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"iss": {"a": "b"}})
58+
59+
def test_claim_sub(self):
60+
claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True})
61+
claims_requests.validate({"sub": "a"})
62+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": 1})
63+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": True})
64+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": ["a"]})
65+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": {"a": "b"}})
66+
67+
def test_claim_aud(self):
68+
claims_requests = jwt.JWTClaimsRegistry(aud={"essential": True})
69+
claims_requests.validate({"aud": "a"})
70+
claims_requests.validate({"aud": ["a"]})
71+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"aud": 1})
72+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"aud": True})
73+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"aud": {"a": "b"}})
74+
5175
def test_essential_empty_value(self):
5276
claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True})
5377
self.assertRaises(MissingClaimError, claims_requests.validate, {"sub": None})
@@ -62,23 +86,23 @@ def test_essential_false_value(self):
6286
claims_requests.validate({"foo": 0})
6387

6488
def test_option_value(self):
65-
claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True, "value": "123"})
66-
self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": "a"})
67-
claims_requests.validate({"sub": "123"})
68-
claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True, "value": True})
69-
claims_requests.validate({"sub": True})
70-
self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": False})
71-
claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True, "value": False})
72-
claims_requests.validate({"sub": False})
73-
self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": True})
89+
claims_requests = jwt.JWTClaimsRegistry(foo={"essential": True, "value": "123"})
90+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"foo": "a"})
91+
claims_requests.validate({"foo": "123"})
92+
claims_requests = jwt.JWTClaimsRegistry(foo={"essential": True, "value": True})
93+
claims_requests.validate({"foo": True})
94+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"foo": False})
95+
claims_requests = jwt.JWTClaimsRegistry(foo={"essential": True, "value": False})
96+
claims_requests.validate({"foo": False})
97+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"foo": True})
7498

7599
def test_option_values(self):
76-
claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True, "values": ["1", "2", True, False]})
77-
self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": "a"})
78-
claims_requests.validate({"sub": "1"})
79-
claims_requests.validate({"sub": "2"})
80-
claims_requests.validate({"sub": True})
81-
claims_requests.validate({"sub": False})
100+
claims_requests = jwt.JWTClaimsRegistry(foo={"essential": True, "values": ["1", "2", True, False]})
101+
self.assertRaises(InvalidClaimError, claims_requests.validate, {"foo": "a"})
102+
claims_requests.validate({"foo": "1"})
103+
claims_requests.validate({"foo": "2"})
104+
claims_requests.validate({"foo": True})
105+
claims_requests.validate({"foo": False})
82106

83107
def test_int_claims(self):
84108
now = int(time.time())

0 commit comments

Comments
 (0)