Azure AD Delegation scenario
tl;dr
- Register a new Web App in AAD for the Api
- Add the required "delegated" permissions to the external resource (i.e: Microsoft Graph)
- Register a new Web App in AAD for the FrontEnd
- Add the permissions to access the Api app
- Configure the Web apps code with the authentication details as usual (ClientID, Client Secret, Audience Uri..etc..). ADAL is the right choice for .NET code
- In Azure portal edit the Api registration manifest adding the GUID of the FronEnd app:
"knownClientApplications": ["XXXXXX-YYYY-ZZZZZZ-WWWWW-XXXXXXX"] - In the Api app code, save the bootstrap token during logon and use it to ask a new "On-Behalf-Of" token for the external resource with the user assertion "urn:ietf:params:oauth:grant-type:jwt-bearer"
"Hello World"!
Protect a web resource with Azure AD is really easy and well documented, but in a delegation scenario where we need to access to an external resource impersonating the signed-in user the things can get little more complicated.
Is not difficult, we just need few extra steps and configuration from the base case.
If I would like to implement an architecture as the following:
- Register a new Web App in AAD for the Api
- Add a redirect URL with "https://localhost"
- Add the required "delegated" permissions to the external resource (i.e: Microsoft Graph)
- Register a new Web App in AAD for the Frontend
- Add redirect URLs to point to the main page
- Add the permissions to access the Api app
- Configure the apps code with the authentication details as usual (ClientID, Client Secret, Audience Uri..etc..)
As you know the first time we access a resource protected by an Oauth based protocol (like OpenIDConnect in AAD) we need to "visually" grant the access to that resource. The classic HTML page that requires the user input to continue. Because at step 2/b we specified the FrontEnd app needs to access the Api app the user will be prompted to confirm the access to both applications (FrontEnd + Api) in a single page.
By default the ADAL library uses the OpenIDConnect "authorization code flow". This means that the FrontEnd app receives an authorization code as part of the login process. This authorization code can be reedemed and exchanged for an access token for the Api app. With ADAL this task can be done in the AuthorizationCodeReceived notification event (in Startup.Auth.cs).
So far so good. We have authenticated the user, received an id_token that identifies the caller, reedemed the authorization code and obtained the access_token for the Api layer. Thus we can access the Api app as the calling user.
Unfortunately this is not sufficent if the Api app needs to access an external resource acting as the signed in user (third hop). This because the token received from this layer is meant only to the Api app and cannot be used to access the external resource (i.e: Microsoft Graph). We need a specific token for this latter resource but there are two major issues at this point:
- The user didn't be prompted for that, so he never granted the access to the external resource
- The Api app is called via code (REST API) and therefore cannot display any HTML page to ask for such grant
In any case we also would like to silently access this resource in a SSO manner without asking again for credentials. Basically we need a way to grant the access to the external resource via the Api layer upfront. Fortunately this is possible manually editing the registration manifest for the Api app in the Azure portal:
"knownClientApplications": ["XXXXXX-YYYY-ZZZZZZ-WWWWW-XXXXXXX"]
Adding the GUID of the FrontEnd app to the "knownClientApplications" property we link the two registrations. This means the first time the user access the FrontEnd app, he will be prompted to grant the access for all the permissions specified by both apps and not only by the FrontEnd one. In other words, we obtain a consensus page that lists all the permissions and all the external resources accessed by the current application and all the linked ones.
Now, as the user grants the access to all the resources upfront, the Api app can request a token for the external resource in a SSO manner. In this case we need to use the "On-Behalf-Of flow" that allows to get an access token that delegates the user credentials. To successfully request this kind token we need to:
- provide the Bootstrap Token (this is just terminology, is the access_token sent by the FrontEnd to the Api app)
- Specify the assertion "urn:ietf:params:oauth:grant-type:jwt-bearer"
if you are using ADAL this translates in two simple steps:
- SaveSigninToken = true in TokenValidationParameters (as by default for performance is discarded after the access)
- Request the token with:
UserAssertion userAssertion = new UserAssertion(bootstrapContext, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
var result = await authContext.AcquireTokenAsync(resourceUri, clientCred, userAssertion);