Open zepor opened 1 month ago
To implement API authentication and authorization protocols using Auth0, JWT, and Azure API Management, we need to configure the necessary settings, integrate Auth0 for authentication, use JWT for token-based authorization, and set up Azure API Management for security. This involves updating several files to ensure secure authentication and authorization for the APIs.
backend-container/flaskapp/config.py
Add the necessary configuration settings for Auth0, including AUTH0_CLIENT_ID
, AUTH0_CLIENT_SECRET
, and AUTH0_DOMAIN
.
import os
from dotenv import load_dotenv
import logging
# Load environment variables from .env file
dotenv_path = os.path.join(os.path.dirname(__file__), '../.env')
load_dotenv(dotenv_path)
class ConfigurationError(Exception):
pass
# Base Configuration
class Config:
DEBUG = False
TESTING = False
connection_string = os.getenv('MONGODB_URL')
if not connection_string:
raise ConfigurationError(
"MONGODB_URL must be set in .env or environment variables")
MONGODB_SETTINGS = {
'db': 'Current_Season',
'host': connection_string,
'maxPoolSize': 50,
'minPoolSize': 5
}
LOGGING_LEVEL = logging.ERROR
# Auth0 Configuration
AUTH0_CLIENT_ID = os.getenv('AUTH0_CLIENT_ID')
AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET')
AUTH0_DOMAIN = os.getenv('AUTH0_DOMAIN')
if not AUTH0_CLIENT_ID or not AUTH0_CLIENT_SECRET or not AUTH0_DOMAIN:
raise ConfigurationError(
"AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, and AUTH0_DOMAIN must be set in .env or environment variables")
class DevelopmentConfig(Config):
DEBUG = True
TESTING = True
LOGGING_LEVEL = logging.DEBUG
class ProductionConfig(Config):
DEBUG = False
TESTING = False
LOGGING_LEVEL = logging.WARNING
backend-container/src/utils/logandcatchexceptions.py
Add specific logging functions for authentication and authorization events.
from functools import wraps
from src.utils.log import be_logger
def log_and_catch_exceptions(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
be_logger.error(f"Exception in {func.__name__}: {e}")
raise
return func_wrapper
def log_authentication_event(event, user_info=None):
if user_info:
be_logger.info(f"Authentication event: {event} for user: {user_info}")
else:
be_logger.info(f"Authentication event: {event}")
def log_authorization_event(event, user_info=None):
if user_info:
be_logger.info(f"Authorization event: {event} for user: {user_info}")
else:
be_logger.info(f"Authorization event: {event}")
# Example usage in authentication and authorization functions
def authenticate_user(user_credentials):
log_authentication_event("User login attempt", user_credentials.get('username'))
# Authentication logic here
log_authentication_event("User login successful", user_credentials.get('username'))
def authorize_user(user, resource):
log_authorization_event("Authorization check", user.get('username'))
# Authorization logic here
log_authorization_event("Authorization granted", user.get('username'))
backend-container/src/utils/errorhandler.py
Add specific error handlers for authentication and authorization errors.
from flask import jsonify
from werkzeug.exceptions import Unauthorized, Forbidden
@app.errorhandler(Exception)
def handle_exception(e):
be_logger.exception("An error occurred: %s", e)
return str(e), 500
@app.errorhandler(Unauthorized)
def handle_unauthorized(e):
be_logger.warning("Unauthorized access attempt: %s", e)
response = jsonify({
"error": "unauthorized",
"message": "You are not authorized to access this resource. Please check your credentials."
})
return response, 401
@app.errorhandler(Forbidden)
def handle_forbidden(e):
be_logger.warning("Forbidden access attempt: %s", e)
response = jsonify({
"error": "forbidden",
"message": "You do not have permission to access this resource."
})
return response, 403
@app.errorhandler(jwt.ExpiredSignatureError)
def handle_expired_token(e):
be_logger.warning("Expired token: %s", e)
response = jsonify({
"error": "token_expired",
"message": "Your session has expired. Please log in again."
})
return response, 401
@app.errorhandler(jwt.InvalidTokenError)
def handle_invalid_token(e):
be_logger.warning("Invalid token: %s", e)
response = jsonify({
"error": "invalid_token",
"message": "The provided token is invalid. Please log in again."
})
return response, 401
backend-container/src/utils/jwt.py
Implement functions to create, verify, and manage JWT tokens securely.
import jwt
import datetime
from typing import Dict, Any
# Constants
SECRET_KEY = 'your_secret_key' # Replace with your actual secret key
ALGORITHM = 'HS256'
def create_token(data: Dict[str, Any], expires_in: int = 3600) -> str:
payload = data.copy()
payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(seconds=expires_in)
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
return token
def verify_token(token: str) -> Dict[str, Any]:
try:
decoded_data = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return decoded_data
except jwt.ExpiredSignatureError:
raise ValueError("Token has expired")
except jwt.InvalidTokenError:
raise ValueError("Invalid token")
def decode_token(token: str) -> Dict[str, Any]:
decoded_data = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM], options={"verify_exp": False})
return decoded_data
backend-container/src/utils/auth.py
Implement the required API authentication and authorization protocols using Auth0.
from flask import Blueprint, redirect, url_for, session
from authlib.integrations.flask_client import OAuth
from dotenv import load_dotenv
import os
# Ensure the environment variables are set
if ENV_FILE := find_dotenv():
load_dotenv(ENV_FILE)
# Blueprint for authentication routes
auth_blueprint = Blueprint('auth_blueprint', __name__)
auth_blueprint.secret_key = os.getenv("APP_SECRET_KEY")
def init_oauth(app):
global oauth
oauth = OAuth()
oauth.init_app(app)
oauth.register(
"auth0",
client_id=os.getenv("AUTH0_CLIENT_ID"),
client_secret=os.getenv("AUTH0_CLIENT_SECRET"),
client_kwargs={
"scope": "openid profile email",
},
server_metadata_url=f'https://{os.getenv("AUTH0_DOMAIN")}/.well-known/openid-configuration'
)
@auth_blueprint.route("/login")
def login():
return oauth.auth0.authorize_redirect(
redirect_uri=url_for("auth_blueprint.callback", _external=True)
)
@auth_blueprint.route("/callback", methods=["GET", "POST"])
def callback():
token = oauth.auth0.authorize_access_token()
session["user"] = token
return redirect("/")
@auth_blueprint.route("/logout")
def logout():
session.clear()
return redirect(
"https://" + os.getenv("AUTH0_DOMAIN")
+ "/v2/logout?"
+ urlencode(
{
"returnTo": url_for("home", _external=True),
"client_id": os.getenv("AUTH0_CLIENT_ID"),
},
quote_via=quote_plus,
)
)
@auth_blueprint.route("/")
def home():
return render_template("home.html", session=session.get('user'), pretty=json.dumps(session.get('user'), indent=4))
backend-container/flaskapp/app.py
Integrate Auth0 authentication into the Flask app and ensure that routes are protected and tokens are verified.
from flask import Flask, render_template, redirect, url_for, session
from flask_cors import CORS
from authlib.integrations.flask_client import OAuth
from dotenv import load_dotenv
import os
import logging
# Load environment variables
load_dotenv()
# Initialize Flask app
app = Flask(__name__, template_folder='../templates')
app.secret_key = os.getenv('FLASK_SECRET_KEY', 'default_secret_key')
app.config['SESSION_COOKIE_SECURE'] = True
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
app.config['JSON_SORT_KEYS'] = False
# Initialize OAuth
oauth = OAuth(app)
auth0 = oauth.register(
'auth0',
client_id=os.getenv('AUTH0_CLIENT_ID'),
client_secret=os.getenv('AUTH0_CLIENT_SECRET'),
api_base_url=os.getenv('AUTH0_DOMAIN'),
access_token_url=os.getenv('AUTH0_DOMAIN') + '/oauth/token',
authorize_url=os.getenv('AUTH0_DOMAIN') + '/authorize',
client_kwargs={
'scope': 'openid profile email',
},
)
# Enable CORS
CORS(app, resources={r"/api/*": {"origins": ["http://localhost:3000", "https://loveoffootball.io"]}})
# Logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
# Auth0 routes
@app.route('/login')
def login():
return auth0.authorize_redirect(redirect_uri=url_for('callback', _external=True))
@app.route('/callback')
def callback():
auth0.authorize_access_token()
resp = auth0.get('userinfo')
userinfo = resp.json()
session['user'] = userinfo
return redirect('/')
@app.route('/logout')
def logout():
session.clear()
return redirect(auth0.api_base_url + '/v2/logout?returnTo=' + url_for('index', _external=True))
@app.route('/')
def index():
return render_template('home.html')
@app.route('/image', methods=['GET'])
def image_page():
return render_template('home.html')
# Protect routes
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'user' not in session:
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated
# Example of a protected route
@app.route('/protected')
@requires_auth
def protected():
return 'This is a protected route.'
if __name__ == "__main__":
FLASK_DEBUG = os.getenv('FLASK_DEBUG', 'true').lower() == 'true'
if FLASK_DEBUG:
app.run(host='0.0.0.0', port=5000, debug=True)
else:
app.run()
README.md
Update the README.md
file with instructions for setting up Auth0, managing JWT tokens, and configuring Azure API Management.
# NFLSTATS Development Environment
This is the NFLSTATS project, designed to provide statistical insights for the NFL.
## THIS APP NEEDS A TON OF WORK
1. PIPELINES FROM SPORTS RADAR APIS FOR LIVE DATA
2. OTHER ADVANCED ANALYTICS DATASOURCES FOR ADDITIONAL ANALYSIS FOR AN AI MODEL
3. DATA VISUALIZATION/DISPLAY
4. ADDING OTHER SPORTS
5. MAKING AI MODELS FOR SPECIFIC GAMES AND THEIR CONFIDENCE INTERVALS IN A PARTICULAR BET
This guide will help you set up the environment using GitHub Codespaces or set it up locally using Docker and Docker Compose. Be aware the application is half built right now. It needs quite a bit of TLC. This is my first project, feel free to criticize and make recommendations as you see fit!
## NFLSTATS Development Environment Setup Guide
Welcome to the NFLSTATS project! This guide will walk you through setting up your development environment to run the application locally. Before diving into the setup process, ensure you have the following prerequisites installed on your machine:
### Prerequisites
- Git: For cloning the repository. Download and install from Git's official site.
- Docker: Required for running the application's containers. Download and install Docker from Docker's official site.
- Docker Compose: For managing multi-container Docker applications. Typically included with Docker Desktop installations.
- Node.js: The runtime environment for the application. Download and install from Node.js's official site.
- npm: Comes with Node.js and is used for managing dependencies.
- Visual Studio Code (Optional): Recommended IDE for editing and debugging the application. Download from Visual Studio Code's official site.
### Environment Setup
#### Clone the Repository
First, clone the NFLSTATS repository to your local machine using Git:
```sh
git clone https://github.com/[your_username]/nflstats.git
Replace [your_username]
with your GitHub username if you have forked the repository. Otherwise, use the project's main repository URL.
Ensure Docker and Docker Compose are installed and running on your machine. The application uses Docker containers for both development (docker-compose.debug.yml
) and production (docker-compose.prod.yml
) environments.
After cloning the repository, navigate to the project directory and install the required npm packages:
cd nflstats
npm install
This command installs all dependencies defined in package.json
.
The application requires several environment variables to be set. These variables can be found and modified in the .env
files located in the project root or specific directories for different environments (e.g., .env.development
, .env.production
). Ensure all necessary variables are set before proceeding.
With all prerequisites installed and environment variables set, you can start the development server using npm scripts defined in package.json
:
npm run start:dev
This command executes the start:dev
script, which builds the application for development and starts the development server. If everything is set up correctly, the application should now be running and accessible.
Once the application is running, you can access it by navigating to http://localhost:3000
in your web browser, assuming the default port configuration.
If the Dockerfiles are on .dockerignore
, ensure to remove them from the ignore list or manually copy them into your project directory. The Dockerfiles are crucial for building the Docker images for both development and production environments.
For detailed information on available npm scripts and their purposes, refer to the scripts section in package.json
. These scripts provide shortcuts for common tasks such as building the application, starting the development server, and cleaning the build directory.
Contributions to the NFLSTATS project are welcome! Please refer to the project's CONTRIBUTING.md
file for guidelines on how to contribute.
To implement secure authentication and authorization for the APIs, follow these steps:
Configure Auth0 for Authentication
Use JWT for Token-Based Authorization
Implement and Configure Azure API Management for Security
Store Auth0 Credentials in GitHub Secrets
AUTH0_CLIENT_ID
, AUTH0_CLIENT_SECRET
Set Up Auth0 Tenant and Application
Client ID
, Client Secret
, and Domain
.Define Roles and Permissions
Configure Environment Variables
.env
file:
AUTH0_CLIENT_ID=your_auth0_client_id
AUTH0_CLIENT_SECRET=your_auth0_client_secret
AUTH0_DOMAIN=your_auth0_domain
Creating JWT Tokens
pyjwt
to create JWT tokens.Verifying JWT Tokens
Set Up API Gateways
Configure Rate Limiting and Throttling Policies
By following this guide, you should now have a fully functional development environment for the NFLSTATS project with secure authentication and authorization implemented. Happy coding!
## Step 7: Integrate Auth0 Authentication into FastAPI
### File: `backend-container/src/nflfeapi/__init__.py`
Integrate Auth0 authentication into the FastAPI application.
```python
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from authlib.integrations.starlette_client import OAuth
import os
app = FastAPI()
oauth = OAuth()
oauth.register(
name='auth0',
client_id=os.getenv('AUTH0_CLIENT_ID'),
client_secret=os.getenv('AUTH0_CLIENT_SECRET'),
server_metadata_url=f'https://{os.getenv("AUTH0_DOMAIN")}/.well-known/openid-configuration',
client_kwargs={
'scope': 'openid profile email',
},
)
def verify_token(token: str):
try:
payload = jwt.decode(token, os.getenv('AUTH0_CLIENT_SECRET'), algorithms=['HS256'], audience=os.getenv('AUTH0_CLIENT_ID'))
return payload
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
return verify_token(token)
@app.get("/protected-route")
async def protected_route(current_user: dict = Depends(get_current_user)):
return {"message": "This is a protected route", "user": current_user}
Click here to create a Pull Request with the proposed solution
Files used for this task:
Implement API Authentication and Authorization Protocols
Description: Implement secure authentication and authorization for APIs. Tasks:
AUTH0_CLIENT_ID
,AUTH0_CLIENT_SECRET
. Milestone: API authentication and authorization implemented.