A Complete Guide to Integrating Okta OpenID Connect SSO with FastAPI and React
A Complete Guide to Integrating Okta SSO with FastAPI and React Okta is a leading identity management solution that simplifies Single Sign-On (SSO) integration. In this guide, we’ll walk through how t
A Complete Guide to Integrating Okta SSO with FastAPI and React
Okta is a leading identity management solution that simplifies Single Sign-On (SSO) integration. In this guide, we’ll walk through how to integrate Okta SSO into a FastAPI backend and a React frontend. By the end of this article, you’ll have a secure and seamless authentication flow in your web application.
1. Setting Up Your Okta Application
To begin, set up an application in Okta:
- Log in to the Okta Developer Console.
- Navigate to Applications → Create App Integration.
- Choose OIDC — OpenID Connect and select Web Application.
- Configure the following:
- Redirect URI: This is the FastAPI endpoint that Okta redirects to after login. Example: http://localhost:7156/signin-oidc.
- Logout URI: Example: http://localhost:5173.
5. Save the app and note down the Client ID, Client Secret, and Issuer URL (we’ll use these later).
2. FastAPI Backend Configuration
Environment Variables
Create a .env file to store sensitive credentials:
OKTA_CLIENT_ID=your-client-id
OKTA_CLIENT_SECRET=your-client-secret
OKTA_ISSUER_URL=https://your-okta-domain.okta.com/oauth2/default
BACKEND_URL=http://localhost:7156
FRONTEND_URL=http://localhost:5173
Dependencies
Install the required libraries:
pip install fastapi uvicorn python-dotenv httpx python-jose
Backend Code
Here’s the core of your main.py file:
from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse, HTMLResponse
import os
from dotenv import load_dotenv
from jose import jwt
import httpx
load_dotenv()
app = FastAPI()
CLIENT_ID = os.getenv("OKTA_CLIENT_ID")
CLIENT_SECRET = os.getenv("OKTA_CLIENT_SECRET")
ISSUER_URL = os.getenv("OKTA_ISSUER_URL")
BACKEND_URL = os.getenv("BACKEND_URL")
FRONTEND_URL = os.getenv("FRONTEND_URL")
# Fetch OpenID Connect metadata
metadata = httpx.get(f"{ISSUER_URL}/.well-known/openid-configuration").json()
authorization_url = metadata["authorization_endpoint"]
token_url = metadata["token_endpoint"]
@app.get("/login")
async def login():
redirect_uri = f"{authorization_url}?client_id={CLIENT_ID}&response_type=code&scope=openid&redirect_uri={BACKEND_URL}/signin-oidc"
return RedirectResponse(url=redirect_uri)
@app.get("/signin-oidc")
async def callback(code: str, response: Response):
async with httpx.AsyncClient() as client:
token_response = await client.post(
token_url,
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": f"{BACKEND_URL}/signin-oidc",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
token_response.raise_for_status()
token_data = token_response.json()
access_token = token_data.get("access_token")
html_content = f"""
<html>
<script>
window.opener.postMessage({{"token": "{access_token}"}}, "*");
window.close();
</script>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
@app.get("/secure-data")
async def secure_data():
return {"message": "You are authenticated!"}
3. React Frontend Configuration
Install Dependencies
Install the necessary React libraries:
npm install axios
Environment Variables
Add the backend base URL to your .env file:
VITE_BASE_REST_URL=http://localhost:7156
Axios Instance
Create a reusable Axios instance for API calls in src/axiosInstance.ts:
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_BASE_REST_URL,
timeout: 300000,
});
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
export default axiosInstance;
Authentication Flow
Modify your App.tsx to handle authentication:
import { useEffect, useState } from 'react';
import axiosInstance from './axiosInstance';
export default function App() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const checkAuthentication = () => {
const token = localStorage.getItem('access_token');
if (token) {
setIsAuthenticated(true);
} else {
initiateLogin();
}
};
const initiateLogin = () => {
const loginUrl = `${import.meta.env.VITE_BASE_REST_URL}/login`;
const loginPopup = window.open(loginUrl, 'Login', 'width=600,height=700');
window.addEventListener('message', (event) => {
if (event.data?.token) {
localStorage.setItem('access_token', event.data.token);
setIsAuthenticated(true);
loginPopup?.close();
}
});
};
useEffect(() => {
checkAuthentication();
}, []);
if (!isAuthenticated) {
return <div>Authenticating...</div>;
}
return <div>Welcome to your application!</div>;
}
4. Testing Your Application
Run the Backend
Run your FastAPI backend using:
uvicorn main:app --reload
Run the Frontend
Run your React app using:
npm run dev
Test the Flow
- Open your app in the browser.
- Ensure the login flow redirects you to Okta and returns to the app with a valid access token.
- Test an API endpoint like /secure-data to verify the token is correctly passed in the Authorization header.
5. Enhancements and Edge Cases
401 Unauthorized Handling: Add a response interceptor to Axios to handle expired tokens:
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response.status === 401) {
localStorage.removeItem('access_token');
window.location.reload();
}
return Promise.reject(error);
}
);
Secure Storage: Use secure alternatives like cookies or encrypted storage for production.
6. Conclusion
This guide covers integrating Okta SSO with FastAPI and React for a seamless authentication experience. By following these steps, you can ensure a secure and scalable solution for your application.
Feel free to leave feedback or share how you implemented this in your projects! 🚀
