Implement a Basic OAuth2 Authorization Code Grant Flow in Python
This challenge requires you to build a simplified implementation of the OAuth2 Authorization Code Grant flow in Python. This flow is fundamental for secure delegated access, allowing users to grant applications permission to access their data on other services without sharing their credentials.
Problem Description
Your task is to create a Python application that simulates a client acting as an OAuth2 service provider and another client acting as an OAuth2 consumer. You will need to implement the core components of the Authorization Code Grant flow:
- Authorization Server (Simulated): This server will handle user authentication (simulated) and issue authorization codes.
- Resource Server (Simulated): This server will hold protected resources and grant access based on valid access tokens.
- Client Application (Consumer): This application will initiate the OAuth2 flow, request an authorization code, exchange it for an access token, and use the access token to access protected resources.
Key Requirements:
- Authorization Endpoint: The server must have an endpoint to redirect the user to for authorization. This endpoint should simulate user authentication and approval.
- Token Endpoint: The server must have an endpoint to receive authorization codes and exchange them for access tokens.
- Client Registration (Implicit): For simplicity, assume clients are pre-registered with a client ID and client secret.
- Access Token Generation: The server should generate a simple, unique access token (e.g., a UUID or a timestamp-based string).
- Resource Endpoint: The server must have an endpoint that serves protected resources, requiring a valid
Authorization: Bearer <access_token>header. - Client-side Flow: The client application should implement the steps to:
- Redirect the user to the authorization endpoint.
- Handle the redirect back from the authorization server with an authorization code.
- Make a POST request to the token endpoint to exchange the authorization code for an access token.
- Use the obtained access token to make a GET request to the resource endpoint.
- Error Handling: Basic error handling for invalid codes or missing tokens should be considered.
Expected Behavior:
The client application should successfully:
- Generate a redirect URL to the authorization server's authorization endpoint with appropriate parameters (
client_id,redirect_uri,response_type=code,scope). - Upon receiving a simulated callback from the authorization server with an
authorization_code, it should post this code along withclient_idandclient_secretto the token endpoint. - If successful, it should receive an
access_tokenfrom the token endpoint. - It should then use this
access_tokenin theAuthorization: Bearer <access_token>header to request data from the resource endpoint. - The resource endpoint should return the protected data if the token is valid.
Edge Cases to Consider:
- Invalid Authorization Code: What happens if the client tries to exchange an invalid or expired authorization code?
- Revoked Access: (Optional, but good to think about) How would the system handle an access token that has been revoked? For this challenge, we'll assume tokens don't expire within the scope of the test.
- Different Scopes: While not strictly required for a basic implementation, consider how scopes might be handled (e.g., the server could validate if the requested scope is allowed for the client).
Examples
Example 1: Successful Flow
Authorization Server Setup:
Assume the following registered client:
client_id:my_awesome_clientclient_secret:super_secret_keyredirect_uri:http://localhost:5000/callback
Client Application Initiates Flow:
The client application constructs the authorization URL:
http://localhost:8000/authorize?client_id=my_awesome_client&redirect_uri=http://localhost:5000/callback&response_type=code&scope=read_profile
Authorization Server Receives Request:
The user (simulated) approves the request. The server generates an authorization code and redirects the client's browser back to the redirect_uri:
http://localhost:5000/callback?code=AUTH_CODE_12345&state=some_random_string
Client Application Receives Authorization Code:
The client application extracts AUTH_CODE_12345.
Client Application Exchanges Code for Token:
The client makes a POST request to http://localhost:8000/token with:
grant_type:authorization_codecode:AUTH_CODE_12345client_id:my_awesome_clientclient_secret:super_secret_keyredirect_uri:http://localhost:5000/callback
Authorization Server Responds with Access Token:
{
"access_token": "ACCESS_TOKEN_XYZ789",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read_profile"
}
Client Application Accesses Protected Resource:
The client makes a GET request to http://localhost:8000/resource with the header:
Authorization: Bearer ACCESS_TOKEN_XYZ789
Resource Server Responds with Data:
{
"user_id": "user123",
"name": "Jane Doe",
"email": "jane.doe@example.com"
}
Explanation: The client successfully navigated the authorization code grant flow, obtaining an access token and using it to retrieve protected user information.
Example 2: Invalid Authorization Code Attempt
Client Application Receives Invalid Code:
The client receives a callback with code=INVALID_CODE.
Client Application Attempts to Exchange Invalid Code:
The client makes a POST request to http://localhost:8000/token with:
grant_type:authorization_codecode:INVALID_CODEclient_id:my_awesome_clientclient_secret:super_secret_keyredirect_uri:http://localhost:5000/callback
Authorization Server Responds with Error:
{
"error": "invalid_grant",
"error_description": "The provided authorization code is invalid or has expired."
}
Explanation: The authorization server correctly rejects the request due to an invalid authorization code.
Constraints
- Your Python implementation should use standard libraries or popular, well-established third-party libraries for web frameworks (e.g., Flask, FastAPI) and HTTP requests (e.g.,
requests). - For simulation purposes, you can use in-memory data structures to store registered clients, authorization codes, and access tokens. No persistent storage is required.
- The simulated user authentication on the authorization server can be a simple
printstatement and a prompt for user input (e.g., "Approve? (y/n)"). - The generated access tokens do not need to be cryptographically secure; a unique string identifier is sufficient.
- The focus is on the flow and correct interaction between the client and the server endpoints.
Notes
- You will likely need to set up two separate Python applications (or use different routes within a single application to simulate two distinct entities): one for the Authorization/Resource Server and one for the Client Application.
- Consider using a simple web framework like Flask or FastAPI to handle HTTP requests and responses for both the server and client.
- The
stateparameter is crucial for preventing CSRF attacks in real-world scenarios. While not mandatory for this basic implementation, you can include it in your redirects and callbacks for good practice. - This challenge simulates the Authorization Code Grant flow. Other OAuth2 flows exist, but this is a common and important one to understand.
- Success looks like a client application that can successfully complete the multi-step process of obtaining an access token and using it to fetch data from a protected resource.