b03ebab747
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
186 lines
5.8 KiB
Markdown
186 lines
5.8 KiB
Markdown
# OAuth Testing with Mock OAuth2 Server
|
|
|
|
This directory contains infrastructure for testing OAuth/OIDC authentication flows using a mock OAuth2 server.
|
|
|
|
## Overview
|
|
|
|
The test setup supports two modes:
|
|
|
|
1. **Mock Mode (Default)**: Uses `mock-oauth2-server` via testcontainers for fast, deterministic tests
|
|
2. **Real Mode**: Uses actual OAuth credentials from GitHub Actions secrets for integration testing
|
|
|
|
## Quick Start
|
|
|
|
### Running Tests with Mock OAuth
|
|
|
|
```bash
|
|
# Run all OAuth integration tests (uses mock by default)
|
|
pytest tests/test_oauth_integration_flows.py -v
|
|
|
|
# Run with coverage
|
|
pytest tests/test_oauth_integration_flows.py --cov=app.auth --cov-report=term-missing
|
|
```
|
|
|
|
### Running Tests with Real OAuth (CI/GitHub Actions)
|
|
|
|
When running in GitHub Actions with secrets configured:
|
|
|
|
```bash
|
|
# Tests automatically detect real credentials and use them
|
|
pytest tests/test_oauth_integration_flows.py -v -m requires_external
|
|
|
|
# Force mock mode even with real credentials available
|
|
USE_MOCK_OAUTH=true pytest tests/test_oauth_integration_flows.py -v
|
|
|
|
# Force real mode (will skip if credentials not available)
|
|
USE_REAL_OAUTH=true pytest tests/test_oauth_integration_flows.py -v
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Components
|
|
|
|
1. **mock_oauth_server.py**: Testcontainers wrapper for mock-oauth2-server
|
|
- Provides complete OIDC endpoints (.well-known, token, userinfo, JWKS)
|
|
- Generates valid JWTs for testing
|
|
- Fast startup (<1s), no persistence needed
|
|
|
|
2. **conftest_oauth.py**: Pytest fixtures for OAuth testing
|
|
- `mock_oauth_server`: Session-scoped mock server fixture
|
|
- `oauth_config`: OAuth configuration (mock or real)
|
|
- `oauth_enabled_app`: Test client with OAuth enabled
|
|
- `oauth_test_token`: Generate test JWT tokens
|
|
- `test_user_info`: Test user claims
|
|
|
|
3. **test_oauth_integration_flows.py**: Integration tests
|
|
- OAuth login flow
|
|
- Token exchange and callback
|
|
- Session management
|
|
- Error handling
|
|
- Real OAuth provider tests (when credentials available)
|
|
|
|
### Mock OAuth Server
|
|
|
|
The mock server (https://github.com/navikt/mock-oauth2-server) provides:
|
|
|
|
- **Authorization endpoint**: `/default/authorize`
|
|
- **Token endpoint**: `/default/token`
|
|
- **Userinfo endpoint**: `/default/userinfo`
|
|
- **JWKS endpoint**: `/default/jwks`
|
|
- **Discovery**: `/default/.well-known/openid-configuration`
|
|
- **Debug token creation**: `/debugger/token`
|
|
|
|
## Usage Examples
|
|
|
|
### Basic OAuth Test
|
|
|
|
```python
|
|
import pytest
|
|
|
|
@pytest.mark.integration
|
|
def test_oauth_login(oauth_enabled_app):
|
|
"""Test OAuth login redirects to provider."""
|
|
response = oauth_enabled_app.get("/oauth-login", follow_redirects=False)
|
|
assert response.status_code == 302
|
|
assert "authorize" in response.headers["location"]
|
|
```
|
|
|
|
### Testing with Mock User
|
|
|
|
```python
|
|
from unittest.mock import patch
|
|
|
|
@pytest.mark.integration
|
|
@patch("app.auth.oauth.authentik.authorize_access_token")
|
|
async def test_oauth_callback(mock_authorize, oauth_enabled_app, test_user_info):
|
|
"""Test OAuth callback with test user."""
|
|
mock_authorize.return_value = {
|
|
"access_token": "test-token",
|
|
"userinfo": test_user_info,
|
|
}
|
|
|
|
response = oauth_enabled_app.get("/oauth-callback?code=test-code")
|
|
assert response.status_code == 302 # Redirects after login
|
|
```
|
|
|
|
### Testing with Generated Token
|
|
|
|
```python
|
|
@pytest.mark.integration
|
|
def test_with_jwt_token(oauth_test_token, test_user_info):
|
|
"""Test with a valid JWT from mock server."""
|
|
# oauth_test_token is a valid JWT signed by the mock server
|
|
# It can be verified using the mock server's JWKS endpoint
|
|
assert oauth_test_token is not None
|
|
print(f"Token for user: {test_user_info['email']}")
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
- `USE_MOCK_OAUTH=true`: Force mock mode
|
|
- `USE_REAL_OAUTH=true`: Force real mode (fails if credentials not available)
|
|
- `AUTHENTIK_CLIENT_ID`: OAuth client ID (for real mode)
|
|
- `AUTHENTIK_CLIENT_SECRET`: OAuth client secret (for real mode)
|
|
- `AUTHENTIK_CONFIG_URL`: OIDC discovery URL (for real mode)
|
|
|
|
### GitHub Actions Secrets
|
|
|
|
When these secrets are set in GitHub Actions, tests automatically use real OAuth:
|
|
|
|
```yaml
|
|
# .github/workflows/test.yml
|
|
env:
|
|
AUTHENTIK_CLIENT_ID: ${{ secrets.AUTHENTIK_CLIENT_ID }}
|
|
AUTHENTIK_CLIENT_SECRET: ${{ secrets.AUTHENTIK_CLIENT_SECRET }}
|
|
AUTHENTIK_CONFIG_URL: ${{ secrets.AUTHENTIK_CONFIG_URL }}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Mock Server Won't Start
|
|
|
|
```bash
|
|
# Check Docker is running
|
|
docker ps
|
|
|
|
# Pull the image manually
|
|
docker pull ghcr.io/navikt/mock-oauth2-server:2.1.1
|
|
|
|
# Check logs
|
|
pytest tests/test_oauth_integration_flows.py -v -s
|
|
```
|
|
|
|
### Tests Hang on Container Startup
|
|
|
|
The mock server fixture waits up to 30 seconds for the server to be ready. If tests hang:
|
|
|
|
1. Check Docker resources (CPU, memory)
|
|
2. Check if port 8080 is available
|
|
3. Try running with `-s` flag to see container logs
|
|
|
|
### Token Validation Fails
|
|
|
|
The mock server generates valid JWTs that can be verified using its JWKS endpoint. If validation fails:
|
|
|
|
1. Ensure the token was created from the correct mock server instance
|
|
2. Check the `aud` (audience) claim matches your client ID
|
|
3. Verify the `iss` (issuer) claim matches the mock server URL
|
|
|
|
## Benefits of This Approach
|
|
|
|
1. **Fast**: Mock server starts in <1s, tests run quickly
|
|
2. **Deterministic**: No external dependencies, same results every time
|
|
3. **Realistic**: Tests actual OAuth flows with real OIDC endpoints
|
|
4. **Flexible**: Can switch to real OAuth for integration tests
|
|
5. **CI-Friendly**: Works in ephemeral CI environments
|
|
6. **Complete**: All OIDC endpoints available for testing
|
|
|
|
## Further Reading
|
|
|
|
- [Mock OAuth2 Server Documentation](https://github.com/navikt/mock-oauth2-server)
|
|
- [Testcontainers Python](https://testcontainers-python.readthedocs.io/)
|
|
- [OAuth 2.0 RFC 6749](https://tools.ietf.org/html/rfc6749)
|
|
- [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html)
|