@@ -75,61 +75,14 @@ class IdTokenNonceError(IdTokenError):
7575 pass
7676
7777def decode_id_token (id_token , client_id = None , issuer = None , nonce = None , now = None ):
78- """Decodes and validates an id_token and returns its claims as a dictionary.
78+ """Decodes an id_token and returns its claims as a dictionary.
7979
8080 ID token claims would at least contain: "iss", "sub", "aud", "exp", "iat",
8181 per `specs <https://openid.net/specs/openid-connect-core-1_0.html#IDToken>`_
8282 and it may contain other optional content such as "preferred_username",
8383 `maybe more <https://openid.net/specs/openid-connect-core-1_0.html#Claims>`_
8484 """
85- decoded = json .loads (decode_part (id_token .split ('.' )[1 ]))
86- # Based on https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
87- _now = int (now or time .time ())
88- skew = 120 # 2 minutes
89-
90- if _now + skew < decoded .get ("nbf" , _now - 1 ): # nbf is optional per JWT specs
91- # This is not an ID token validation, but a JWT validation
92- # https://tools.ietf.org/html/rfc7519#section-4.1.5
93- _IdTokenTimeError ("0. The ID token is not yet valid." , _now , decoded ).log ()
94-
95- if issuer and issuer != decoded ["iss" ]:
96- # https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
97- raise IdTokenIssuerError (
98- '2. The Issuer Identifier for the OpenID Provider, "%s", '
99- "(which is typically obtained during Discovery), "
100- "MUST exactly match the value of the iss (issuer) Claim." % issuer ,
101- _now ,
102- decoded )
103-
104- if client_id :
105- valid_aud = client_id in decoded ["aud" ] if isinstance (
106- decoded ["aud" ], list ) else client_id == decoded ["aud" ]
107- if not valid_aud :
108- raise IdTokenAudienceError (
109- "3. The aud (audience) claim must contain this client's client_id "
110- '"%s", case-sensitively. Was your client_id in wrong casing?'
111- # Some IdP accepts wrong casing request but issues right casing IDT
112- % client_id ,
113- _now ,
114- decoded )
115-
116- # Per specs:
117- # 6. If the ID Token is received via direct communication between
118- # the Client and the Token Endpoint (which it is during _obtain_token()),
119- # the TLS server validation MAY be used to validate the issuer
120- # in place of checking the token signature.
121-
122- if _now - skew > decoded ["exp" ]:
123- _IdTokenTimeError ("9. The ID token already expires." , _now , decoded ).log ()
124-
125- if nonce and nonce != decoded .get ("nonce" ):
126- raise IdTokenNonceError (
127- "11. Nonce must be the same value "
128- "as the one that was sent in the Authentication Request." ,
129- _now ,
130- decoded )
131-
132- return decoded
85+ return json .loads (decode_part (id_token .split ('.' )[1 ]))
13386
13487
13588def _nonce_hash (nonce ):
@@ -158,9 +111,7 @@ class Client(oauth2.Client):
158111
159112 def decode_id_token (self , id_token , nonce = None ):
160113 """See :func:`~decode_id_token`."""
161- return decode_id_token (
162- id_token , nonce = nonce ,
163- client_id = self .client_id , issuer = self .configuration .get ("issuer" ))
114+ return decode_id_token (id_token )
164115
165116 def _obtain_token (self , grant_type , * args , ** kwargs ):
166117 """The result will also contain one more key "id_token_claims",
@@ -193,20 +144,14 @@ def obtain_token_by_authorization_code(self, code, nonce=None, **kwargs):
193144 plus new parameter(s):
194145
195146 :param nonce:
196- If you provided a nonce when calling :func:`build_auth_request_uri`,
197- same nonce should also be provided here, so that we'll validate it.
198- An exception will be raised if the nonce in id token mismatches .
147+ Optional. If you provided a nonce when calling
148+ :func:`build_auth_request_uri`, you may still pass it here for
149+ backward compatibility .
199150 """
200151 warnings .warn (
201152 "Use obtain_token_by_auth_code_flow() instead" , DeprecationWarning )
202- result = super (Client , self ).obtain_token_by_authorization_code (
153+ return super (Client , self ).obtain_token_by_authorization_code (
203154 code , ** kwargs )
204- nonce_in_id_token = result .get ("id_token_claims" , {}).get ("nonce" )
205- if "id_token_claims" in result and nonce and nonce != nonce_in_id_token :
206- raise ValueError (
207- 'The nonce in id token ("%s") should match your nonce ("%s")' %
208- (nonce_in_id_token , nonce ))
209- return result
210155
211156 def initiate_auth_code_flow (
212157 self ,
@@ -249,42 +194,13 @@ def obtain_token_by_auth_code_flow(self, auth_code_flow, auth_response, **kwargs
249194 """Validate the auth_response being redirected back, and then obtain tokens,
250195 including ID token which can be used for user sign in.
251196
252- Internally, it implements nonce to mitigate replay attack.
253197 It also implements PKCE to mitigate the auth code interception attack.
254198
255199 See :func:`oauth2.Client.obtain_token_by_auth_code_flow` in parent class
256200 for descriptions on other parameters and return value.
257201 """
258- result = super (Client , self ).obtain_token_by_auth_code_flow (
202+ return super (Client , self ).obtain_token_by_auth_code_flow (
259203 auth_code_flow , auth_response , ** kwargs )
260- if "id_token_claims" in result :
261- nonce_in_id_token = result .get ("id_token_claims" , {}).get ("nonce" )
262- expected_hash = _nonce_hash (auth_code_flow ["nonce" ])
263- if nonce_in_id_token != expected_hash :
264- raise RuntimeError (
265- 'The nonce in id token ("%s") should match our nonce ("%s")' %
266- (nonce_in_id_token , expected_hash ))
267-
268- if auth_code_flow .get ("max_age" ) is not None :
269- auth_time = result .get ("id_token_claims" , {}).get ("auth_time" )
270- if not auth_time :
271- raise RuntimeError (
272- "13. max_age was requested, ID token should contain auth_time" )
273- now = int (time .time ())
274- skew = 120 # 2 minutes. Hardcoded, for now
275- if now - skew > auth_time + auth_code_flow ["max_age" ]:
276- raise RuntimeError (
277- "13. auth_time ({auth_time}) was requested, "
278- "by using max_age ({max_age}) parameter, "
279- "and now ({now}) too much time has elasped "
280- "since last end-user authentication. "
281- "The ID token was: {id_token}" .format (
282- auth_time = auth_time ,
283- max_age = auth_code_flow ["max_age" ],
284- now = now ,
285- id_token = json .dumps (result ["id_token_claims" ], indent = 2 ),
286- ))
287- return result
288204
289205 def obtain_token_by_browser (
290206 self ,
@@ -299,7 +215,6 @@ def obtain_token_by_browser(
299215 ** kwargs ):
300216 """A native app can use this method to obtain token via a local browser.
301217
302- Internally, it implements nonce to mitigate replay attack.
303218 It also implements PKCE to mitigate the auth code interception attack.
304219
305220 :param string display: Defined in
@@ -334,4 +249,3 @@ def obtain_token_by_browser(
334249 return super (Client , self ).obtain_token_by_browser (
335250 auth_params = dict (kwargs .pop ("auth_params" , {}), ** filtered_params ),
336251 ** kwargs )
337-
0 commit comments