Intility / fastapi-azure-auth

Easy and secure implementation of Azure Entra ID (previously AD) for your FastAPI APIs 🔒 B2C, single- and multi-tenant support.
https://intility.github.io/fastapi-azure-auth
MIT License
416 stars 64 forks source link

[BUG/Question] Example use cases for scopes #186

Closed dom-gunstone closed 1 week ago

dom-gunstone commented 4 months ago

Hi,

Firstly, thanks for the work on this project. It's going to be really useful.

Would you be able to describe some examples for where and why you would use scopes, please? The documentation describes the creation of a user_impersonation scope that allows users to consent to the application using the API as the user, but I can't think of any more use cases.

The Entra docs are particularly confusing on scopes...

Thanks.

bmoore commented 4 months ago

The way scopes ( https://oauth.net/2/scope/ ) are used by entra is as delegated access. By granting a client the scope on an API, it's saying that when the client application passes a user access token to an API, the user consented the client application access to the user's data regarding that scope on that API.

An example is Mail.Read in entra. If a client had the Mail.Read scope, then the client would only have permissions to get the signed in user's mail messages when passing a user access toke (read more about on-behalf-of flow https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow ).

This is contrary to the Application Role Mail.Read which gives the client (client credential flow https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow ) access to all Mail in the entra tenant.

Your API may act differently, but the concept would be scopes allow access to just the user's data related to the scope of access.

I'm writing from my phone and will follow up with some links.

A lot of this comes down to how you define the flow in your code

dom-gunstone commented 4 months ago

Thanks @bmoore, that's really helpful. I think I'm struggling to apply the scope mechanism to a "real world" FastAPI example.

This is probably a terrible example (this is all in my head), but, if I had a Book API with basic CRUD operations I would create roles in Entra e.g.:

I can then give roles to users/groups etc. and have the FastAPI app check for roles.

For scopes would I define them like this?:

Is this correct?: The scopes define what the user has consented the application to do on their behalf. The roles are what "Entra admins" have decided a user has access to. Scopes are a subset of permissions, a user may have many but only allow one.

Does using scopes in the Book API example even make sense? Consenting to user personal information intuitively makes sense, but consenting to reading/writing book information doesn't since it's not necessarily the user's data?

bmoore commented 4 months ago

To answer your questions, yes it makes sense to me, if the books are "owned" or "shared".

A quick way to think about it: Roles for a user on an api can play a part in defining what permissions a user has on that API. Scopes are the consenting piece from the user to share the users API permission with the client.

In your case, a user with the Reader Role on the API might only be allowed to read 2 books in the whole collection with licensing policies.

Some book reading UI client that works with your Book API could be granted Book.Read by the user. The, when the UI client goes to fetch books, the API sees the consented scope, parses the user token, sees the role, gets books where the user is licensed to read, and returns the books.

Consider a change from "Book" to "Document".

Quick response from top of my head. User roles can be seen more like application roles where it provides RBAC authorization to resources at large. Scopes would likely have more specific names, if you wanted to grant granular access (read vs readwrite) but the difference, from my understanding, comes down to checking whether the user is authorized to access a given resource.

Consider a Document. If a client makes a read call to an API's document endpoint on behalf of the user, the API checks roles and scopes. The API can(read should) check whether a Document scope exists on the token before getting documents. If there's a document.read scope, then when processing the request, the API should make sure the user owns (or otherwise has) access to the document by some policy you define.

Part of that policy could be checking for a user role, maybe something like Document Admin that allows the user to read and write all documents. But on the case that their role is just User, the API would check for owner / share rights.

I hope that's a decent example. Just spinning from the top of my head

dom-gunstone commented 4 months ago

That's great information, thank you! It's much clearer in my head now.

I think the docs example through me off a bit with user_impersonation. In you opinion, is there a need for this if I have more granular scopes?

bmoore commented 4 months ago

If someone knows the details, please chime in, but from my experience, you don't need that scope if you plan on using others.

user_impersonation to me represents all possible scopes the API can offer. So if you had lots of scopes that would overload the token, and you had a super client (i.e Azure Portal) that uses all of them, maybe user_impersonation would be a useful scope.

sstorey-bma commented 2 months ago

Is it possible to use the library to authenticate, BUT then inject the scopes allowed for an user (post authentication from Azure). If possible, we would want to manage user roles/groups/scopes outside of Entra (and stored within our application so we can segment this across environments etc), but authenticate the user with Entra. Any thoughts/ideas on how we could achieve this?

JonasKs commented 1 week ago

Thank you @bmoore for the follow up here!

In general, most applications does not need to use scopes, and roles will be sufficient. When the use case for scopes is there, it will most of the time be pretty obvious, and the OBO flow bmoore explains is one of them.

@sstorey-bma , yes, this is absolutely possible, and require you to use auto_error=False. I haven't really documented this use-case, but the setting exist, which allow you to chain dependencies, as shown in the demo project. It basically works by saying "do not raise an exception when the user can't be verified". In order to achieve what you would like, you could do something like this: