dudil / fastapi_msal

A FastAPI Plug-In to support authentication authorization using the Microsoft Authentication Library (MSAL)
MIT License
40 stars 20 forks source link

Dependency overrides in unit testing #12

Closed u-iandono closed 2 years ago

u-iandono commented 2 years ago

First of all, thank you so much for making this great integration. You make it so easy to use AzureAD with FastAPI!

And there is a stale discussion about this topic here: https://github.com/dudil/fastapi_msal/issues/4

Since it's already closed, I thought I would create a new issue with more details because I think I'm encountering the same issue.

So, I'd like to write unit testing for the /user/me endpoint. But since it has a dependency on the msal_auth.scheme, I need to bypass the authentication process.

msal_client_config: MSALClientConfig = MSALClientConfig()
...

msal_auth = MSALAuthorization(client_config=msal_client_config, return_to_path=settings.base_url)
msal_auth_user = Depends(msal_auth.scheme)

@router.get('/me', response_model=UserInfo, response_model_exclude_none=True, response_model_by_alias=False)
async def get_user_me(current_user: UserInfo = msal_auth_user) -> UserInfo:
    return current_user

I've tried to follow the tutorial about overriding dependencies from the FastAPI tutorial here. But it seems to be not working as the current_user still returns {'detail': 'Not authenticated'}

This is how I override the dependencies

def override_msal_scheme() -> UserInfo:
    return UserInfo(
        first_name='Test',
        last_name='User',
        display_name='Test User',
        emails=['test_user@email.com'],
        user_id='test_user_id',
    )

@pytest.fixture(scope="module")
def client() -> Generator:
    with TestClient(app) as c:
        app.dependency_overrides[msal_auth.scheme] = override_msal_scheme
        yield c

If you can provide an example of how to bypass, silently override, or bypass the authentication process, that would be really helpful!

u-iandono commented 2 years ago

Hi, I figured out how to make this work.

Basically, instead of using msal_auth.scheme directly as the dependency, you can create a custom function dependency that uses msal_auth.scheme as the dependency. And then you can use this function as your dependency.

Here's my dependency.py file.

msal_client_config: MSALClientConfig = MSALClientConfig()
...

msal_auth = MSALAuthorization(client_config=msal_client_config, return_to_path=settings.base_url)

def authenticated_user(current_user: UserInfo = Depends(msal_auth.scheme)) -> UserInfo:
    return current_user

And here is my users.py that uses the authenticated_user function as the dependency.

@router.get('/me', response_model=UserInfo, response_model_exclude_none=True, response_model_by_alias=False)
async def get_user_me(current_user: UserInfo = Depends(authenticated_user)) -> UserInfo:
    return current_user

There is not much difference in the conftest.py, only some adjustments in how to create the UserInfo object.

def override_authenticated_user() -> UserInfo:
    return UserInfo(
        name='Test User',
        oid='11111111-1111-1111-1111-111111111111',
        preferred_username='test_user@email.com',
    )

@pytest.fixture(scope='module')
def client() -> Generator:
    with TestClient(app) as test_client:
        app.dependency_overrides[authenticated_user] = override_authenticated_user
        yield test_client