I am trying to configure WebApp (MSAL + Razor Pages) application to use two app registrations – one for frontend, another one for backend. The main reason for this is, that I am planning to migrate to SPA in a future and would like to have transition as smooth as possible (without requesting new consents/new app registrations). I have configured them in similar way as I do for SPA applications (two registrations, all required API permissions on backend, frontend registration configured in "knownClientApplications" of backend. System has frontend registration used to authenticate user with scope api://{backendClientId}/.default scope. Everything works well, consent is properly propagated to backend registration.
I am experiencing problems with OBO flow. I tried to configure downstream API to use client id of backend server, but call to graph API failed with insufficient privileges. Following is the configuration of the server side:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(o =>
{
o.Instance = "https://login.microsoftonline.com/";
o.Domain = "{MyDomain}";
o.TenantId = "common";
o.ClientId = "{FrontendClientId}";
o.ClientSecret = "{FrontendClientSecret}";
o.CallbackPath = "/signin-oidc";
o.Scope.Add("api://{BackendClientId}/.default");
})
.EnableTokenAcquisitionToCallDownstreamApi(o =>
{
o.Instance = "https://login.microsoftonline.com/";
o.TenantId = "common";
o.ClientId = "{BackendClientId}";
o.ClientSecret = "{BackendClientSecret}";
})
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
What am I doing wrong? Is the setup desired by me even possible?
Thanks a lot for the help.
2
Answers
Yes, it’s possible. And, from a security perspective is desirable.
In an OBO flow you shouldn’t be using the backend ClientId or ClientSecret anywhere in your frontend. Your backend should be obtaining an OBO token of the frontend identity for use by the backend API.
AddMicrosoftIdentityWebApp(o => ...)
sets the credentials or token to authenticate to the backend API.EnableTokenAcquisitionToCallDownstreamApi
sets the OBO token to be passed to, and used by, the backend.So, in this scenario, the backend will be using the frontend identity to call the Graph API. Therefore, it is the backend principal which needs the relevant delegated Graph permissions. The frontend is delegating its credential to the backend. The frontend needs the app permissions to Graph for the backend to be able to perform the necessary action. For example, if the frontend needs to read a user’s name:
Frontend Permission: Type
App
, PermissionUser.ReadBasic.All
Backend Permission: Type
Delegated
, PermissionUser.ReadBasic.All
An example where you might further refine security is where all the users signed in to your frontend are Entra users, and instead of using the frontend token, you use the signed in user’s token. You do this with
acquireTokenSilent
, and then your frontend doesn’t need explicit Graph permissions unless it is doing something which the user isn’t permitted. But this is for another post.You can update your code as follows:
Here’s an example of token acquisition for your downstream API call:
Here’s an example of validation of the front end calling credential, and OBO token acquisition in the downstream API:
This downstream function API is a bit rushed, but you should get the idea.
To resolve permission not enough issue, I’m afraid that we could set the client to the known client applications list in the backend application.
Then when we sign in the client app and in the consent popup window, we require to consent for the backend app. Using
openid api://backend_app_client_id/.default
as the scope when generate the access token in the client app.