pythonfastapi

Why adding custom_openapi scema to FastAPI is causing the authorisation not to work?


I have this FastAPI app working in the main.py:

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

sub_app1 = FastAPI()
sub_app1.include_router(auth.router)

and in the routers/auth.py I have:

@router.post("/complete", status_code=status.HTTP_201_CREATED)
async def complete_registration_create_user(db: db_dependency, create_user_request: CreateUserRequest):
    """Compeletes the registration when user submits password."""
    hashed_password = bcrypt_context.hash(create_user_request.password)
    create_user_model = Users(
        email=create_user_request.email,
        hashed_password=hashed_password,
        phone=create_user_request.phone,
    )

    # Note: In a organisation all staff members should be approved by Owner.

    try:
        db.add(create_user_model)
        await db.commit()
    except IntegrityError as er:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"User {create_user_request.email} already exists. Choose a different email or try resetting the password."
        )
    await db.refresh(create_user_model)
    return {"user": create_user_request.email}


@router.post("/token", response_model=Token)
async def user_login_for_access_token(form_data: Annotated[OAuth2EmailRequestForm, Depends()],
                                db: db_dependency):
    """Login user for an access token"""
    user = await authenticate_user(form_data.email, form_data.password, db)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate user."
        )

    tenant_identifier = form_data.identifier

    token = create_access_token(
        user.email,
        user.id,
        tenant_identifier,
        timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    )

    return {'access_token': token, 'token_type': 'bearer'}

all above works but as soon as I add the below udpate to specify openschema to main.py:

def custom_openapi():
    if sub_app1.openapi_schema:
        return sub_app1.openapi_schema

    openapi_schema = get_openapi(
        title=" Authorisation API",
        version="0.0.1",
        description="API on this docs deals with all user and company registration.",
        routes=sub_app1.routes,
    )
    sub_app1.openapi_schema = openapi_schema
    return sub_app1.openapi_schema

sub_app1.openapi = custom_openapi

the auth endpoints starts resulting in Not found error i.e.

INFO:     127.0.0.1:43756 - "POST /register/token?email=user2%40example.com&identifier=sada&password=string HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:44748 - "POST /register/complete HTTP/1.1" 404 Not Found

any help will be greatly appreciated, I am stuck here.


Solution

  • If you want to mount a new FastAPI app, you should use

    app.mount("/register", sub_app1)
    

    But in your case, seems like you don't need this at all. I'm sure you just need to set APIRouter:

    sub_app1 = APIRouter()
    sub_app1.include_router(auth.router)
    
    app.include_router(sub_app1, prefix="/register")
    

    After this, you could apply custom OpenAPI function on app:

    app.openapi = custom_openapi
    

    Finally, your code may look like this:

    from fastapi import FastAPI, APIRouter
    from fastapi.middleware.cors import CORSMiddleware
    from fastapi.openapi.utils import get_openapi
    app = FastAPI()
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"]
    )
    sub_app1 = APIRouter()
    sub_app1.include_router(auth.router)
    app.include_router(sub_app1,prefix="/register")
    
    def custom_openapi():
        if app.openapi_schema:
            return app.openapi_schema
        openapi_schema = get_openapi(
            title="Authorisation API",
            version="0.0.1",
            description="API dealing with user and company registration.",
            routes=app.routes,
        )
        app.openapi_schema = openapi_schema
        return app.openapi_schema
    
    app.openapi = custom_openapi
    

    Mounts documentation

    Including routes documentation

    But maybe you really need to mount app for some reason? Could you provide more details?