Creating an OAuth 2.0 authorization server from scratch involves understanding the OAuth 2.0 framework and implementing its various components, such as authorization endpoint, token endpoint, and client registration. In this step-by-step tutorial, we’ll walk through building a simple OAuth 2.0 authorization server using Python 3 and Flask, a popular web framework. This server will manage basic OAuth flows, including client registration, authorization code flow, and access token issuance.
Setting up your environment
First, make sure you have Python 3 installed on your system. You will also need pip
to install the Python package.
1. Create a virtual environment
python3 -m venv oauth-server-env
source oauth-server-env/bin/activate # On Windows, use `oauth-server-env\Scripts\activate`
2. Install Flask
3. Install other necessary packages
pip install Flask-HTTPAuth PyJWT
Flask-HTTPAuth
will help authenticate the client and PyJWT
is used to create JSON Web Tokens (JWTs), which will serve as our access tokens.
Project structure
Create a new directory for your project (oauth_server
) and create the following files:
app.py
: Main application fileclient_registry.py
: A simple registry for storing client informationauth_server.py
: Contains the logic for the OAuth 2.0 authorization server
Implementation of the Client Registry
Let’s start with implementing a basic client registry. This registry will store client details and provide methods for registering new clients and verifying client credentials.
client_registry.py
import uuid
clients =
def register_client(client_name):
client_id = str(uuid.uuid4())
client_secret = str(uuid.uuid4())
clients[client_id] = 'client_secret': client_secret, 'client_name': client_name
return client_id, client_secret
def validate_client(client_id, client_secret):
return client_id in clients and clients[client_id]['client_secret'] == client_secret
This registry uses embedded Python uuid
library for generating unique identifiers for client IDs and secrets.
Building the authorization server
Next, we will implement OAuth 2.0 endpoints in our authorization server.
auth_server.py
from flask import Flask, request, jsonify, redirect, url_for
from client_registry import register_client, validate_client
import jwt
import datetime
app = Flask(__name__)
@app.route('/register', methods=['POST'])
def register():
client_name = request.json.get('client_name')
client_id, client_secret = register_client(client_name)
return jsonify('client_id': client_id, 'client_secret': client_secret)
@app.route('/authorize')
def authorize():
# In a real application, you would validate the user here
client_id = request.args.get('client_id')
redirect_uri = request.args.get('redirect_uri')
# Generate an authorization code
auth_code = str(uuid.uuid4())
# Redirect back to the client with the auth code
return redirect(f"redirect_uri?code=auth_code")
@app.route('/token', methods=['POST'])
def token():
auth_code = request.form.get('code')
client_id = request.form.get('client_id')
client_secret = request.form.get('client_secret')
if not validate_client(client_id, client_secret):
return jsonify('error': 'invalid_client'), 401
# In a real application, you'd validate the auth code here
# Generate JWT as access token
payload =
'client_id': client_id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30) # 30 min expiry
access_token = jwt.encode(payload, 'secret', algorithm='HS256')
return jsonify('access_token': access_token)
if __name__ == '__main__':
app.run(debug=True)
This server offers three endpoints:
/register
: Allows clients to register and receive theirsclient_id
andclient_secret
/authorize
: Simulates the authorization step where the user approves the client; In a real-world application, this would include user authentication and consent./token
: Changes the authorization code for the access token; Here we use JWT as our access token format.
Starting your authorization server
Run your application:
Your OAuth 2.0 authorization server is now up and ready to process requests.
Flow testing
Testing the OAuth 2.0 authorization flow within a web application involves a series of steps to ensure that the integration not only adheres to OAuth 2.0 specifications, but also effectively protects user data. The goal of the test is to simulate the entire OAuth process from initiation to acquisition of an access token, and optionally a refresh token, imitating the actions that a real user would take.
Setting up the test environment
Before testing the OAuth 2.0 authorization flow, make sure your test environment is set up correctly. This includes:
- Authorization server: Your OAuth 2.0 authorization server should be fully configured and running. For Python3 applications, this can be a Flask or Django application that you set up according to the OAuth 2.0 specifications.
- Client application: A client web application configured to request authorization from the server; This is usually another web application that will use OAuth for authentication.
- User accounts: Test user accounts on the authorization server with predefined permissions or roles
- Secure connection: Ensure that all communication is over HTTPS, even in your test environment, to accurately simulate real-world conditions.
Step-by-step testing procedure
1. Start the authorization request
From the client application, run an authorization request. This typically involves directing the user’s browser to an authorization URL constructed with the necessary query parameters such as response_type
, client_id
, redirect_uri
, scope
and optional state
parameter for CSRF protection.
2. Authenticate and authorize
- The user should be redirected to the authorization server login page, where they can enter their credentials.
- Upon successful authentication, the user should be presented with a consent screen (if applicable) where they can authorize the requested permissions.
- Make sure it is
state
the client correctly validates the parameter, if used, after redirection.
3. Authorization response
- After the user consents, the authorization server should redirect the user back to the client application using
redirect_uri
which includes the authorization code in the query parameters. - Provide redirection to
redirect_uri
is happening correctly and the authorization code is present in the request.
4. Token exchange
- The client application then needs to exchange the authorization code for an access token (and optionally a refresh token) by sending a request to the authorization server’s token endpoint.
- Verify that the token exchange requires authentication (client ID and secret) and is conducted over HTTPS.
- Verify that the access token (and refresh token, if applicable) is returned in the response.
5. Use of access token
- The client application should use the access token to make authenticated requests to the resource server on behalf of the user.
- Ensure that resources can be accessed using the access token and that requests without a valid access token are denied.
6. Refresh token process (if applicable)
- If a refresh token has been issued, simulate an access token expiration and use the refresh token to obtain a new access token.
- Verify that the new access token grants access to resources as expected.
Security and compliance testing
Invalid request processing
Test how the authorization server handles invalid requests, such as missing parameters or incorrect client credentials. The server should respond with an appropriate response to the error.
Token revocation and expiration
Ensure that expired or revoked tokens are properly handled and do not allow access to protected resources.
Scope validation
Verify that the scope of the access tokens is respected and that the tokens only grant access to the resources they are supposed to.
Automated testing tools
Consider using automated testing tools and frameworks designed for OAuth 2.0 to simplify the testing process. Tools such as Postman, OAuth 2.0 Test Server (enabled by OAuth.tools), and custom scripts can automate many of the steps involved in testing an OAuth flow.
Preparing the environment for testing
- Setting up an authorization server: Let’s assume we have a Flask-based OAuth 2.0 authorization server running at http://127.0.0.1:5000.
- Setting up the client application: A Django web application that acts as an OAuth 2.0 client, registered with an authorization server with
client_id
fromabc123
i.aclient_secret
. - Secure connection: Both applications implement HTTPS
Test 1: Run an authorization request
The client application redirects the user to the authorization server with a URL structured as follows:
http://127.0.0.1:5000/oauth/authorize?response_type=code&client_id=abc123&redirect_uri=https://client.example.com/callback&scope=read&state=xyz
Expected outcome
The user is redirected to the authorization server login page.
Detailed steps and checks
- Redirection to authorization server: Ensure that the user’s browser is redirected to the correct URL on the authorization server.
- Valid query parameters: Make sure the URL contains all the required parameters (
response_type
,client_id
,redirect_uri
,scope
andstate
).
Test 2: User authentication and authorization
After redirection, the user logs in with their credentials and grants the requested permissions.
Expected outcome
The authorization server redirects the user back to the client application’s callback URL with the authorization code.
Detailed steps and checks
- Application and consent: After signing in, check that the consent screen displays the requested permissions correctly (based on
scope
). - Redirect with authorization code: Make sure the server redirects to http://127.0.0.1:5000/callback?code=AUTH_CODE&state=xyz.
- State parameter validation: Confirm that the client application validates the returned
state
parameter corresponds to the one sent in the initial request.
Test 3: Exchange authorization code for access token
The client application exchanges an authorization code for an access token.
Sample request
Using Python requests
library, the client application sends a POST request to the token endpoint:
import requests
data =
'grant_type': 'authorization_code',
'code': 'AUTH_CODE',
'redirect_uri': 'http://127.0.0.1:5000/callback',
'client_id': 'abc123',
'client_secret': 'secret'
response = requests.post('http://127.0.0.1:5000/oauth/token', data=data)
Expected outcome
The authorization server responds with an access token.
Detailed steps and checks
- Valid Token Endpoint: Verify that the POST request is directed to the correct token endpoint URL.
- Successful token response: Make sure the answer includes
access_token
(and optionally arefresh_token
).
Test 4: Using an access token
The client application uses the access token to request resources from the resource server on behalf of the user.
An example of a resource request
headers = 'Authorization': f'Bearer access_token'
response = requests.get('http://127.0.0.1:5000/userinfo', headers=headers)
Expected outcome
The resource server returns the requested user information.
Detailed steps and checks
- Access token in authorization header: Confirm that the access token is included in the request header.
- Valid resource access: Check the response from the resource server that contains the expected data.
Test 5: Refresh access token
Assuming a refresh_token
is obtained, simulate an access token expiration and refresh it.
Example of a refresh request
data =
'grant_type': 'refresh_token',
'refresh_token': 'REFRESH_TOKEN',
'client_id': 'abc123',
'client_secret': 'secret'
response = requests.post('http://127.0.0.1:5000/oauth/token', data=data)
Expected outcome
A new access token is issued by the authorization server.
Detailed steps and checks
- Successful token refresh: Ensure that the response contains a new one
access_token
. - Valid access with new token: Verify that the new access token can access the resources.
Conclusion
Testing the OAuth 2.0 authorization flow is essential to ensure the security and functionality of your web application’s authentication mechanism. A thorough testing process not only validates the implementation against the OAuth 2.0 specification, but also identifies potential security vulnerabilities that could compromise user data. By following a structured testing approach, you can ensure users have a secure and seamless authentication experience.