Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: How to protect API routes without UI login #16

Closed
soderluk opened this issue Nov 23, 2020 · 10 comments · Fixed by #17
Closed

Question: How to protect API routes without UI login #16

soderluk opened this issue Nov 23, 2020 · 10 comments · Fixed by #17

Comments

@soderluk
Copy link
Contributor

First of all, thanks for your work on the library!

I'm struggling a bit trying to understand the usage of the library though.

How does one protect an API route without the need to have any UI?
I'm reading the docs, but cannot for the life of me figure out how to use this for a plain API.

The authentication works well when using the /docs endpoint, but e.g. using insomnia and reading endpoints, I just can't get the authentication to work.

Having the dependency in the route auth_state: AuthenticationState = Depends(auth_provider.api_auth_scheme) shows the auth_state as None.

In my main.py I use

auth_provider = AADAuth()
app = FastAPI(
        title=PROJECT_TITLE,
        debug=DEBUG,
        version=VERSION,
        swagger_ui_init_oauth=auth_provider.api_auth_scheme.init_oauth,
    )
auth_provider.configure_app(app)

in my routes:

@router.get("/", response_model=List[schemas.Model])
async def list_models(
    skip: int = 0,
    limit: int = 100,
    auth_state: AuthenticationState = Depends(auth_provider.api_auth_scheme),
):
    stored = await domain.Model.find_all(skip, limit)
    models = [schemas.Model(**model) for model in stored]
    return JSONResponse(content=jsonable_encoder(models))

Any help appreciated!

@djpugh
Copy link
Owner

djpugh commented Nov 23, 2020

Ah yes - repro'd, will try and fix tonight.

@djpugh
Copy link
Owner

djpugh commented Nov 23, 2020

The fix in #17 means that this should work correctly, I'd missed something off in a refactoring!

@tricosmo
Copy link

tricosmo commented Nov 25, 2020

@djpugh thanks for fixing this. @soderluk I am just wondering if you get it working or not . Cause I still have some issues.

I am using the example for here, with an .env file in local.

import logging

logging.basicConfig(level="DEBUG")

from fastapi import APIRouter, Depends
from fastapi import FastAPI
from starlette.responses import HTMLResponse, PlainTextResponse, RedirectResponse
from starlette.requests import Request
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import request_response, Route
import uvicorn


from fastapi_aad_auth import __version__, AADAuth, AuthenticationState

auth_provider = AADAuth()

router = APIRouter()


@router.get("/hello")
async def hello_world(
    auth_state: AuthenticationState = Depends(auth_provider.api_auth_scheme),
):
    print(auth_state)
    return {"hello": "world"}


if "untagged" in __version__ or "unknown":
    API_VERSION = 0
else:
    API_VERSION = __version__.split(".")[0]


async def homepage(request):
    if request.user.is_authenticated:
        return PlainTextResponse("Hello, " + request.user.display_name)
    return HTMLResponse(f"<html><body><h1>Hello, you</h1><br></body></html>")


@auth_provider.auth_required()
async def test(request):
    if request.user.is_authenticated:
        return PlainTextResponse("Hello, " + request.user.display_name)


routes = [Route("/", endpoint=homepage), Route("/test", endpoint=test)]


app = FastAPI(
    title="fastapi_aad_auth test app",
    description="Testapp for Adding Azure Active Directory Authentication for FastAPI",
    version=__version__,
    openapi_url=f"/api/v{API_VERSION}/openapi.json",
    docs_url="/api/docs",
    swagger_ui_init_oauth=auth_provider.api_auth_scheme.init_oauth,
    redoc_url="/api/redoc",
    routes=routes,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:8000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


app.include_router(router)

# comment out cause I want anonymous user to access the swagger UI
# auth_provider.configure_app(app)


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", debug=True, port=8000, log_level="debug")

I got this error but don't know where I can dig the root cause.
image
I found the callback request looks normal.
INFO: 127.0.0.1:35936 - "GET /docs/oauth2-redirect?code=0.ASEADg3ayTXy...85-e6434cfaadde HTTP/1.1" 200 OK
but frontend shows this error.

Update: this is caused by CORS.

Also another question, as you can see it ask users to input client_id and client_secret in the form whereas most of users don't know what it is. I am thinking if there is a way to configure client_id and client_secret in the backend, so end users will just need to click the authorize button.

any help from you guys would be highly appreciated.

@soderluk
Copy link
Contributor Author

@tricosmo: No I haven't tested the fix yet. I'm also just providing a pure API for any frontend to use, so the HTML things are not working out for me anyway. I need to figure out the endpoints how to initialize the auth flow to Azure from the frontend, instead of using the UI provided by fastapi_aad_auth.

Also I find it quite frustrating, that when I enable the authentication for the docs, I have to log in every 3rd refresh or so... I think I just have to dig deeper into the module at some point.

Right now I'm focusing on getting everything else ready in the API and focus on the auth later.

@djpugh
Copy link
Owner

djpugh commented Nov 26, 2020

@tricosmo

Also another question, as you can see it ask users to input client_id and client_secret in the form whereas most of users don't know what it is. I am thinking if there is a way to configure client_id and client_secret in the backend, so end users will just need to click the authorize button.

That is provided by fastapi and the swaggerui component that's being used there - the values can/should be preconfigured on the UI if they're loaded in via the environement

@djpugh
Copy link
Owner

djpugh commented Nov 26, 2020

@soderluk - in terms of needing to login again, I'm assuming you mean when you are restarting the development server? You can set (as env variables or in a .env file):

SESSION_AUTH_SECRET="****"
SESSION_AUTH_SALT="****"
SESSION_SECRET="****"

Which will ensure restarting the server doesn't reset the cookie sessions

@tricosmo
Copy link

tricosmo commented Nov 27, 2020

thanks @djpugh. I got it working now.
Previously I didn't configure Azure app registration correctly, used web instead of single page application in the platform section. Just wondering do you have any plan to pump the version up. thanks

I have the same login again problem. It happens for every refresh of the swagger page. (server was running always)

@soderluk
Copy link
Contributor Author

@djpugh: The dev server is running all the time, and it still requires me to log in almost every time I refresh the docs.

Also same question as @tricosmo, are you going to bump the pypi version?

@djpugh
Copy link
Owner

djpugh commented Nov 28, 2020

Released in 0.1.4

@soderluk
Copy link
Contributor Author

soderluk commented Nov 30, 2020

@djpugh I'm still struggling a bit how to do this whole thing without a UI... I mean I only have my API, and use Insomnia (or Postman) to interact with the API.

Could you give some pointers, as to how I should go about to actually initialize the auth flow to Azure, then use a token in the headers to be able to use the endpoints?

And as a bonus, does this module provide a callback, so that the user model could be persisted in the DB?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants