test: implement mock OAuth2 server infrastructure for auth testing
- Add MockOAuth2ServerContainer using testcontainers - Create conftest_oauth.py with OAuth test fixtures - Add comprehensive OAuth integration tests - Support both mock (default) and real (CI secrets) OAuth modes - Add documentation for OAuth testing setup Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user