@@ -42,7 +42,7 @@ def decode_id_token(id_token, client_id=None, issuer=None, nonce=None, now=None)
4242 """
4343 decoded = json .loads (decode_part (id_token .split ('.' )[1 ]))
4444 err = None # https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
45- _now = now or time .time ()
45+ _now = int ( now or time .time () )
4646 skew = 120 # 2 minutes
4747 if _now + skew < decoded .get ("nbf" , _now - 1 ): # nbf is optional per JWT specs
4848 # This is not an ID token validation, but a JWT validation
@@ -67,14 +67,14 @@ def decode_id_token(id_token, client_id=None, issuer=None, nonce=None, now=None)
6767 # the Client and the Token Endpoint (which it is during _obtain_token()),
6868 # the TLS server validation MAY be used to validate the issuer
6969 # in place of checking the token signature.
70- if _now > decoded ["exp" ]:
70+ if _now - skew > decoded ["exp" ]:
7171 err = "9. The current time MUST be before the time represented by the exp Claim."
7272 if nonce and nonce != decoded .get ("nonce" ):
7373 err = ("11. Nonce must be the same value "
7474 "as the one that was sent in the Authentication Request." )
7575 if err :
76- raise RuntimeError ("%s The id_token was: %s" % (
77- err , json .dumps (decoded , indent = 2 )))
76+ raise RuntimeError ("%s Current epoch = %s. The id_token was: %s" % (
77+ err , _now , json .dumps (decoded , indent = 2 )))
7878 return decoded
7979
8080
@@ -187,6 +187,8 @@ def initiate_auth_code_flow(
187187 flow = super (Client , self ).initiate_auth_code_flow (
188188 scope = _scope , nonce = _nonce_hash (nonce ), ** kwargs )
189189 flow ["nonce" ] = nonce
190+ if kwargs .get ("max_age" ) is not None :
191+ flow ["max_age" ] = kwargs ["max_age" ]
190192 return flow
191193
192194 def obtain_token_by_auth_code_flow (self , auth_code_flow , auth_response , ** kwargs ):
@@ -208,6 +210,26 @@ def obtain_token_by_auth_code_flow(self, auth_code_flow, auth_response, **kwargs
208210 raise RuntimeError (
209211 'The nonce in id token ("%s") should match our nonce ("%s")' %
210212 (nonce_in_id_token , expected_hash ))
213+
214+ if auth_code_flow .get ("max_age" ) is not None :
215+ auth_time = result .get ("id_token_claims" , {}).get ("auth_time" )
216+ if not auth_time :
217+ raise RuntimeError (
218+ "13. max_age was requested, ID token should contain auth_time" )
219+ now = int (time .time ())
220+ skew = 120 # 2 minutes. Hardcoded, for now
221+ if now - skew > auth_time + auth_code_flow ["max_age" ]:
222+ raise RuntimeError (
223+ "13. auth_time ({auth_time}) was requested, "
224+ "by using max_age ({max_age}) parameter, "
225+ "and now ({now}) too much time has elasped "
226+ "since last end-user authentication. "
227+ "The ID token was: {id_token}" .format (
228+ auth_time = auth_time ,
229+ max_age = auth_code_flow ["max_age" ],
230+ now = now ,
231+ id_token = json .dumps (result ["id_token_claims" ], indent = 2 ),
232+ ))
211233 return result
212234
213235 def obtain_token_by_browser (
0 commit comments