@@ -49,14 +49,18 @@ class TokenRequest(RootModel):
4949 Field (discriminator = "grant_type" ),
5050 ]
5151
52- AUTH_CODE_TTL = 300 # seconds
5352
5453def create_token_handler (
5554 provider : OAuthServerProvider , client_authenticator : ClientAuthenticator
5655) -> Callable :
5756 def response (obj : TokenSuccessResponse | TokenErrorResponse ):
57+ status_code = 200
58+ if isinstance (obj , TokenErrorResponse ):
59+ status_code = 400
60+
5861 return PydanticJSONResponse (
5962 content = obj ,
63+ status_code = status_code ,
6064 headers = {
6165 "Cache-Control" : "no-store" ,
6266 "Pragma" : "no-cache" ,
@@ -98,8 +102,7 @@ async def token_handler(request: Request):
98102
99103 # make auth codes expire after a deadline
100104 # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.5
101- expires_at = auth_code .issued_at + AUTH_CODE_TTL
102- if expires_at < time .time ():
105+ if auth_code .expires_at < time .time ():
103106 return response (TokenErrorResponse (
104107 error = "invalid_grant" ,
105108 error_description = f"authorization code has expired"
@@ -130,12 +133,34 @@ async def token_handler(request: Request):
130133 )
131134
132135 case RefreshTokenRequest ():
136+ refresh_token = await provider .load_refresh_token (client_info , token_request .refresh_token )
137+ if refresh_token is None or refresh_token .client_id != token_request .client_id :
138+ # if the authoriation code belongs to a different client, pretend it doesn't exist
139+ return response (TokenErrorResponse (
140+ error = "invalid_grant" ,
141+ error_description = f"refresh token does not exist"
142+ ))
143+
144+ if refresh_token .expires_at and refresh_token .expires_at < time .time ():
145+ # if the authoriation code belongs to a different client, pretend it doesn't exist
146+ return response (TokenErrorResponse (
147+ error = "invalid_grant" ,
148+ error_description = f"refresh token has expired"
149+ ))
150+
133151 # Parse scopes if provided
134- scopes = token_request .scope .split (" " ) if token_request .scope else None
152+ scopes = token_request .scope .split (" " ) if token_request .scope else refresh_token .scopes
153+
154+ for scope in scopes :
155+ if scope not in refresh_token .scopes :
156+ return response (TokenErrorResponse (
157+ error = "invalid_scope" ,
158+ error_description = f"cannot request scope `{ scope } ` not provided by refresh token"
159+ ))
135160
136161 # Exchange refresh token for new tokens
137162 tokens = await provider .exchange_refresh_token (
138- client_info , token_request . refresh_token , scopes
163+ client_info , refresh_token , scopes
139164 )
140165
141166 return response (tokens )
0 commit comments