FastAPI Integration Guide¶
This guide demonstrates how to integrate Evrmore Authentication into a FastAPI application. FastAPI is a modern, fast web framework for building APIs with Python that's well-suited for projects requiring blockchain authentication.
Prerequisites¶
- Python 3.8+
- FastAPI and its dependencies (
pip3 install fastapi uvicorn) evrmore-authenticationpackage installed- Basic understanding of FastAPI and asynchronous Python
Project Setup¶
Create a new directory for your FastAPI project:
Create a virtual environment and install the required packages:
python3 -m venv venv
source venv/bin/activate
pip3 install fastapi uvicorn evrmore-authentication python-multipart
Basic FastAPI Integration¶
1. Create the Main Application¶
Create a file named main.py with the following content:
from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from pydantic import BaseModel
from typing import Optional
from evrmore_authentication import EvrmoreAuth
# Initialize FastAPI app
app = FastAPI(title="Evrmore Auth API", description="API with Evrmore Authentication")
# Add CORS middleware to allow frontend integration
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, specify your frontend domains
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Initialize Evrmore Authentication
auth = EvrmoreAuth(
jwt_secret="your-secret-key", # Use a secure secret in production
debug=True # Set to False in production
)
# Create a Pydantic model for authentication requests
class AuthRequest(BaseModel):
evrmore_address: str
challenge: Optional[str] = None
signature: Optional[str] = None
# Create a Pydantic model for token validation
class TokenRequest(BaseModel):
token: str
@app.get("/")
async def read_root():
return {"message": "Welcome to Evrmore Authentication API"}
2. Add Challenge Generation Endpoint¶
Add a challenge generation endpoint to the main.py file:
@app.post("/generate-challenge")
async def generate_challenge(request: AuthRequest):
"""Generate a challenge for the provided Evrmore address"""
try:
challenge = auth.generate_challenge(request.evrmore_address)
return {"status": "success", "challenge": challenge}
except Exception as e:
return JSONResponse(
status_code=400,
content={"status": "error", "message": str(e)}
)
3. Add Authentication Endpoint¶
Add an authentication endpoint to the main.py file:
@app.post("/authenticate")
async def authenticate(request: AuthRequest):
"""Authenticate a user with their address, challenge, and signature"""
try:
session = auth.authenticate(
evrmore_address=request.evrmore_address,
challenge=request.challenge,
signature=request.signature
)
return {
"status": "success",
"user_id": session.user_id,
"token": session.token,
"expires_at": str(session.expires_at)
}
except Exception as e:
return JSONResponse(
status_code=401,
content={"status": "error", "message": str(e)}
)
4. Add Token Validation Endpoints¶
Add token validation endpoints to the main.py file:
@app.post("/validate-token")
async def validate_token(request: TokenRequest):
"""Validate a JWT token"""
token_data = auth.validate_token(request.token)
if token_data:
return {
"status": "success",
"user_id": token_data.get("sub"),
"evrmore_address": token_data.get("evr_address")
}
else:
return JSONResponse(
status_code=401,
content={"status": "error", "message": "Invalid token"}
)
@app.post("/logout")
async def logout(request: TokenRequest):
"""Invalidate a JWT token (logout)"""
success = auth.invalidate_token(request.token)
if success:
return {"status": "success", "message": "Logged out successfully"}
else:
return JSONResponse(
status_code=400,
content={"status": "error", "message": "Failed to log out"}
)
5. Add Protected Routes with Authentication Dependency¶
Create authentication dependencies and protected routes:
# Authentication dependency
async def get_current_user(request: Request):
# Get token from Authorization header
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing or invalid token")
token = auth_header.replace("Bearer ", "")
token_data = auth.validate_token(token)
if not token_data:
raise HTTPException(status_code=401, detail="Invalid or expired token")
# Get the user by ID
user_id = token_data.get("sub")
user = auth.get_user_by_id(user_id)
if not user:
raise HTTPException(status_code=401, detail="User not found")
return user
# Protected route example
@app.get("/protected")
async def protected_route(user = Depends(get_current_user)):
"""This endpoint requires authentication"""
return {
"status": "success",
"message": "You have access to this protected resource",
"user_id": user.id,
"evrmore_address": user.evrmore_address
}
6. Run the Application¶
Add a main block to run the application:
Complete Example¶
The complete main.py file should look like this:
from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from pydantic import BaseModel
from typing import Optional
from evrmore_authentication import EvrmoreAuth
# Initialize FastAPI app
app = FastAPI(title="Evrmore Auth API", description="API with Evrmore Authentication")
# Add CORS middleware to allow frontend integration
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, specify your frontend domains
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Initialize Evrmore Authentication
auth = EvrmoreAuth(
jwt_secret="your-secret-key", # Use a secure secret in production
debug=True # Set to False in production
)
# Create a Pydantic model for authentication requests
class AuthRequest(BaseModel):
evrmore_address: str
challenge: Optional[str] = None
signature: Optional[str] = None
# Create a Pydantic model for token validation
class TokenRequest(BaseModel):
token: str
@app.get("/")
async def read_root():
return {"message": "Welcome to Evrmore Authentication API"}
@app.post("/generate-challenge")
async def generate_challenge(request: AuthRequest):
"""Generate a challenge for the provided Evrmore address"""
try:
challenge = auth.generate_challenge(request.evrmore_address)
return {"status": "success", "challenge": challenge}
except Exception as e:
return JSONResponse(
status_code=400,
content={"status": "error", "message": str(e)}
)
@app.post("/authenticate")
async def authenticate(request: AuthRequest):
"""Authenticate a user with their address, challenge, and signature"""
try:
session = auth.authenticate(
evrmore_address=request.evrmore_address,
challenge=request.challenge,
signature=request.signature
)
return {
"status": "success",
"user_id": session.user_id,
"token": session.token,
"expires_at": str(session.expires_at)
}
except Exception as e:
return JSONResponse(
status_code=401,
content={"status": "error", "message": str(e)}
)
@app.post("/validate-token")
async def validate_token(request: TokenRequest):
"""Validate a JWT token"""
token_data = auth.validate_token(request.token)
if token_data:
return {
"status": "success",
"user_id": token_data.get("sub"),
"evrmore_address": token_data.get("evr_address")
}
else:
return JSONResponse(
status_code=401,
content={"status": "error", "message": "Invalid token"}
)
@app.post("/logout")
async def logout(request: TokenRequest):
"""Invalidate a JWT token (logout)"""
success = auth.invalidate_token(request.token)
if success:
return {"status": "success", "message": "Logged out successfully"}
else:
return JSONResponse(
status_code=400,
content={"status": "error", "message": "Failed to log out"}
)
# Authentication dependency
async def get_current_user(request: Request):
# Get token from Authorization header
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing or invalid token")
token = auth_header.replace("Bearer ", "")
token_data = auth.validate_token(token)
if not token_data:
raise HTTPException(status_code=401, detail="Invalid or expired token")
# Get the user by ID
user_id = token_data.get("sub")
user = auth.get_user_by_id(user_id)
if not user:
raise HTTPException(status_code=401, detail="User not found")
return user
# Protected route example
@app.get("/protected")
async def protected_route(user = Depends(get_current_user)):
"""This endpoint requires authentication"""
return {
"status": "success",
"message": "You have access to this protected resource",
"user_id": user.id,
"evrmore_address": user.evrmore_address
}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
Running the Application¶
Run the FastAPI application:
The API will be available at http://localhost:8000.
Testing with Swagger UI¶
FastAPI provides automatic interactive API documentation with Swagger UI. You can access it at http://localhost:8000/docs.
Using the Swagger UI, you can:
- Try the
/generate-challengeendpoint to get a challenge - Use your Evrmore wallet to sign the challenge (or use the development tools for testing)
- Submit the signature to the
/authenticateendpoint to get a token - Use the token to access the
/protectedendpoint
Frontend Integration¶
To integrate this API with a frontend application, you can use the Swagger UI as a reference for the required API calls. Here's an example of how you might implement the authentication flow in a JavaScript frontend:
// Example frontend code (JavaScript)
async function authenticate(evrmoreAddress) {
// Step 1: Generate a challenge
const challengeResponse = await fetch('/generate-challenge', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ evrmore_address: evrmoreAddress })
});
const challengeData = await challengeResponse.json();
if (challengeData.status !== 'success') {
throw new Error(challengeData.message);
}
// Step 2: Sign the challenge with the Evrmore wallet
// This depends on your wallet integration
const signature = await signWithWallet(evrmoreAddress, challengeData.challenge);
// Step 3: Authenticate with the signature
const authResponse = await fetch('/authenticate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
evrmore_address: evrmoreAddress,
challenge: challengeData.challenge,
signature: signature
})
});
const authData = await authResponse.json();
if (authData.status !== 'success') {
throw new Error(authData.message);
}
// Store the token for future requests
localStorage.setItem('authToken', authData.token);
return authData;
}
// Example of how to make authenticated requests
async function fetchProtectedResource() {
const token = localStorage.getItem('authToken');
if (!token) {
throw new Error('Not authenticated');
}
const response = await fetch('/protected', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return await response.json();
}
// Example of how to log out
async function logout() {
const token = localStorage.getItem('authToken');
if (!token) {
return { status: 'success', message: 'Already logged out' };
}
const response = await fetch('/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ token })
});
const data = await response.json();
if (data.status === 'success') {
localStorage.removeItem('authToken');
}
return data;
}
Next Steps¶
After implementing this basic FastAPI integration, you can:
- Add more protected routes and resources
- Implement user profiles and additional user data
- Add refresh token functionality
- Integrate with a database for persistent storage
- Add rate limiting and other security features
- Implement OAuth 2.0 for third-party applications
For OAuth 2.0 integration, see the OAuth client example documentation.