I’m trying to get authentication working from frontend and authenticate on the backend.
Everything works when I click on authorise button in swagger, I’m logging in using google.
When I log in from frontend, I also get redirected to google login and then I do callback and
I get in api:
INFO: 127.0.0.1:51442 - "GET /api/v1/login/callback?code=4/0AdLIrYe0bPYpa6fq5wv6zFE-8Tm-ZsHK83XVOVhptSsu1qX1REA_rtvNzhy0mWEJpdZkoQ&scope=email%20profile%20openid%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/userinfo.email&authuser=0&prompt=consent HTTP/1.1" 200 OK
and in frontend:
TypeError: null is not an object (evaluating 'window.opener.swaggerUIRedirectOauth2')
from fastapi import FastAPI, Depends, APIRouter
from fastapi.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2AuthorizationCodeBearer
from .configuration.logging_config import setup_logging
from .routers import user, group, auth
from .config import AUTHORIZATION_URL, TOKEN_URL, SCOPES
setup_logging()
oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl=AUTHORIZATION_URL,
tokenUrl=TOKEN_URL,
scopes={scope: "" for scope in SCOPES}
)
v1_router = APIRouter(prefix="/v1")
v1_router.include_router(user.router, prefix="/users", tags=["users"], dependencies=[Depends(oauth2_scheme)])
v1_router.include_router(group.router, prefix="/groups", tags=["groups"], dependencies=[Depends(oauth2_scheme)])
v1_router.include_router(auth.router, prefix="", tags=["auth"])
app = FastAPI(
title="My app",
description="",
version="1.0.0",
openapi_url="/api/v1/openapi.json",
docs_url="/api/v1/docs",
redoc_url="/api/v1/redoc",
swagger_ui_oauth2_redirect_url="/api/v1/login/callback",
)
app.include_router(v1_router, prefix="/api")
@app.get("/health")
def health_check():
return {"status": "healthy"}
@app.get("/")
async def root():
return RedirectResponse(url="/api/v1/docs")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(SessionMiddleware, secret_key="supersecret")
import logging
from fastapi import Request, APIRouter
from fastapi.responses import RedirectResponse
from smart_spendings.config import REDIRECT_URI, CLIENT_ID, CLIENT_SECRET, AUTHORIZATION_URL, SCOPES, TOKEN_URL
from authlib.integrations.starlette_client import OAuth, OAuthError
import httpx
from fastapi import HTTPException, APIRouter, Request, status
router = APIRouter()
oauth = OAuth()
oauth.register(
name='google',
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
client_kwargs={
'scope': 'openid email profile',
'redirect_uri': REDIRECT_URI,
'verify': False,
'response_type': 'code',
'code_challenge_method': 'S256'
}
)
@router.get("/login")
async def login():
return RedirectResponse(
url=f"{AUTHORIZATION_URL}?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope=" + "%20".join(
SCOPES)
)
@router.get("/login/callback")
async def retrieve_user(request: Request, code: str):
async with httpx.AsyncClient() as client:
params = {
"grant_type": "authorization_code",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"redirect_uri": REDIRECT_URI,
"code": code
}
response = await client.post(TOKEN_URL, data=params)
if response.status_code != 200:
logger.error(f"Failed to fetch tokens: {response.text}")
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Failed to authenticate.")
token_data = response.json()
logger.info(f"Token data received: {token_data}")
refresh_token = token_data.get("refresh_token")
access_token = token_data.get('access_token')
redirect_uri = f"http://localhost:5173/#access_token={access_token}"
return RedirectResponse(url=redirect_uri, status_code=status.HTTP_303_SEE_OTHER)
@router.get('/logout')
def logout(request: Request):
request.session.clear()
return {"message": "Logged out successfully"}
import { Admin } from 'react-admin';
import dataProvider from './dataProvider';
import { Dashboard } from './Dashboard';
import authProvider from './authProvider';
import {useEffect} from "react";
const App = () => {
useEffect(() => {
const { hash } = window.location;
const accessToken = hash.split('=')[1];
if (accessToken) {
localStorage.setItem('auth', accessToken);
window.location.href = 'http://localhost:5173/#/';
}
}, []);
return (
<Admin
authProvider={authProvider}
dashboard={Dashboard}
dataProvider={dataProvider}
>
</Admin>
)
}
export default App;
// src/authProvider.ts
const authProvider = {
login: ({ username, password }) => {
// Redirect to FastAPI /login endpoint
window.location.href = 'http://localhost:8000/api/v1/login';
return Promise.resolve();
},
logout: () => {
localStorage.removeItem('auth');
return Promise.resolve();
},
checkAuth: () => {
return localStorage.getItem('auth') ? Promise.resolve() : Promise.reject();
},
checkError: (error) => Promise.resolve(),
getPermissions: () => Promise.resolve(),
};
export default authProvider;
import { fetchUtils } from 'react-admin';
import { stringify } from 'query-string';
export const apiUrl = import.meta.env.VITE_API_BASE_URL;
export const httpClient = (url: string, options: RequestInit = {}) => {
const modifiedHeaders = new Headers(options.headers ?? { Accept: 'application/json' });
// Use 'auth' instead of 'token' here
const token = localStorage.getItem('auth');
if (token) {
modifiedHeaders.append('Authorization', `Bearer ${token}`);
}
return fetchUtils.fetchJson(url, { ...options, headers: modifiedHeaders });
}
const dataProvider = {
getList: (resource, params) => {
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const query = {
sort: JSON.stringify([field, order]),
range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]),
filter: JSON.stringify(params.filter),
};
const url = `${apiUrl}/${resource}/?${stringify(query)}`;
return httpClient(url).then(({ headers, json }) => ({
data: json,
total: parseInt(headers.get("content-range").split("/").pop(), 10),
}));
},
create: (resource, params) => {
const url = `${apiUrl}/${resource}`;
return httpClient(url, {
method: 'POST',
body: JSON.stringify(params.data)
}).then(({ json }) => ({
data: { ...json, id: json.expense_id } // map expense_id from API to id
}));
},
delete: (resource, params) => {
const url = `${apiUrl}/${resource}/${params.id}`;
return httpClient(url, {
method: 'DELETE'
}).then(({ json }) => ({ data: json }));
},
// Other methods like getOne, getMany, getManyReference, update, etc. go here
};
export default dataProvider;
I have one REDIRECT_URI = "http://localhost:8000/api/v1/login/callback"
that I use in backend and frontend.
Also I don’t understand why previously I had always swagger_ui_oauth2_redirect_url="/api/v1/docs/oauth2-redirect"
, and now I had to change, to make work backend, into swagger_ui_oauth2_redirect_url="/api/v1/login/callback"
.
Let me know if you know what I’m doing wrong and how to solve TypeError: null is not an object (evaluating 'window.opener.swaggerUIRedirectOauth2')
.
I’m expecting to see my react admin page and some resources that I have.
I tried changing redirect uri and OAuth2 settings but it didn’t work.