API Security with ASP.NET Core 5.0 and Azure AD for Dummies
This blog is part of a complete blog series.
- Part 1: Authentication vs authorization
- Part 2: The different actors
- Part 3: Authentication with Azure AD
- Part 4: Authorization with Access Control List
- Part 5: Authorization with Application Roles
- Part 6: Authorization with Delegated Permissions (this blog)
- Part 7: Retrieve more user information
- Part 8: Access APIs on behalf of a user (coming soon)
- Part 9: Automate the Azure AD configuration (coming soon)
Scopes have been introduced with OAuth2, to support real delegation scenarios. A typical example is a user that allows a picture application to access her/his files inside OneDrive. It’s important that the application gets a subset of the user permission. Delegation scenarios are often in the context of multi-tenant applications, so it is important to ensure that the application can only access the data of the user (resource owner) and not the data of other users. Scopes are often misunderstood and misused, leading to security threats. This blog nicely explains this concern.
API security metaphor
When getting back to our party metaphor, delegated permissions can be used to access the private parties. You need explicit permission of the private party owner, in order to access her/his party.
- The visitor asks Jon if he may go to his party. Jon agrees and signs a paper to confirm this.
- The ticket desk gives a badge that shows permission to access the party and Jon’s private party.
- The bodyguard only allows people with a valid badge that contains the permission to access they party.
- The bodyguard of Jon’s party only grants access, if the badge indicates Jon’s consent to his party.
Let’s try to translate this into the API security vocabulary:
- The application asks the user to consent access to her/his private party
- Azure AD returns an access token (JWT) that contains the access_as_user and access_private_party scope
- The API has a global security to only allow access tokens with the access_as_user scope
- The private party operations grants access to the party of the users that consented
Delegated Permissions are ideal for these security requirements:
- There is always a user logged in
- There is a real delegation scenario (access on behalf of a user)
- The authorization must be fine-grained and potentially multi-tenant
Azure AD Configuration
Remember that we already configure a generic scope, access_as_user, to allow user interaction. This scope was consented by the Azure AD administrator, because it did not grant permission to perform user specific actions. In order to access the private party, we want to enable explicit user consent.
- Navigate to the party-api App Registration
- In the Expose an API tab, click on `+ Add a scope`
- Configure the scope as shown below and click Add scope
Inspect access tokens
- Open the Postman configuration for the User App, as described in Part 3.
- Change the scope to: https://party-api/access_as_user https://party-api/access_private_party
- Click on Get New Access Token and log in (if needed)
- Grant consent to access your private party, by clicking Accept
- Click Proceed, copy the access token and click Use Token
- You can inspect the access token on jwt.ms
- You should see that the two scopes are present now
Validate permissions on the API
Global scope validation
The first global validation that we have to do, is enforcing the presence of the access_as_user scope. This ensures that a Azure AD administrator has granted the client application access.
- Open the PartyApi solution from the `02-with-authentication` folder
- Update the AuthorizationPolicyBuilder in the Startup.cs file, to check on the mandatory access_as_user scope
var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .RequireAssertion(handler => { var scopeClaim = handler.User.FindFirst("http://schemas.microsoft.com/identity/claims/scope"); var scopes = scopeClaim?.Value.Split(' '); var hasScope = scopes?.Where(scope => scope == "access_as_user").Any() ?? false; return hasScope; }) .Build();
- From now on, calling the API will only succeed if the access token contains the access_as_user scope
Specific scope validation
Additionally, we must secure the GetIntoThePrivateParty() operation. This can only be called when the access_private_party scope is present, which means user consent has been granted.
- Update the GetIntoThePrivateParty(), by adding and chaning the bold statements:
HttpContext.VerifyUserHasAnyAcceptedScope("access_private_party"); return new PartyExperience { Date = DateTime.Now, Location = "private-party", Status = PartyStatuses[new Random().Next(PartyStatuses.Length)], AdditionalInfo = $"You have access to the private party of {User.FindFirst("name")?.Value}." };
- From now on, calling the party/private-party operation will only succeed if the access token contains the access_private_party scope
Conclusion
We have seen how delegated permissions can be used with admin consent or user consent. It’s important to understand that this authorization mechanism could be used in combination with role-based access control. Roles could be used to access the party zones and VIP area, whilst delegated permissions are introduced to grant access to the private parties on behalf of the party owner.