I got a console app that needs to support two flows, authenticating against AAD to talk to a web api:
- for regular usage by humans, it needs to support interactive login
- for usage by a CI/CD pipeline it needs to support client-credentials.
The interactive flow works perfectly, but the client-credentials flow is giving me problems with the requested scope.
I'm using the latest Microsoft.Identity*
nuget packages.
When I construct the scope for the webapi like I do for the interactive flow, I get an error message telling me that for client-credentials flows I need to append ./default
. Okay, fair enough, I also found documentation for this, so I append ./default
. But when I do that, I get another error message telling me
The resource principal named api:///access_as_user was not found in the tenant named .
There are two problems with this error message:
- the resource principal quoted definitely exists - also, I couldn't login interactively if it didn't, but as mentioned, interactive login works just fine
- it says
api://<webApiAppId>/access_as_user
, notapi://<webApiAppId>/access_as_user/.default
, despite my appending that
My next though was: well, maybe the problem is that the app registration used for the client-credentials flow doesn't have permissions on the web-api. But it does.
So now I've run out of ideas. Hopefully, someone here can help.
To make everything a bit clearer, let me list the app regs involved:
A. Web Api
- Was setup via the VS Wizard/dotnet-msidentity tool
- has a few App Roles defined
- exposes a single API
api://<itsownAppId>/access_as_user
B. Interactive Login
- manually created
- redirect URI for localhost
- API Permissions: added
WebApi | access_as_user
as delegated
C. Non-interactive login/Service Principal
- setup manually
- is used also for other things by the CI/CD pipeline
- has a ClientSecret defined
- API Permissions: added
WebApi | access_as_user
with 2 of the app roles defined for A - has other API Permissions that have nothing to do with this here (for Graph)
- granted admin consent for all permissions
The code I use to authenticate is (for the confidential flow):
ConfidentialClientApplicationBuilder.Create(_configuration.ApplicationId)
.WithTenantId(_configuration.Directory)
.WithLogging(Log, LogLevel.Error)
.WithClientSecret(_configuration.ClientSecret)
.Build()
.AcquireTokenForClient(_configuration.Scopes)
.ExecuteAsync();
where the values of _configuration
are:
ApplicationId
: the appId from app registration CDirectory
: the name of my AAD tenantClientSecret
: the secret from app registration CScopes
: array ofopenid
,profile
andapi://<appIdOfWebApiFromC>/access_as_user/.default
So, it turns out that the documentation for appending
./default
is not quite clear enough:.You are not meant to append it to the scope, just to the "resource id". And with resource id they mean the "api://" parts without the name of the permission.So where you normally request
api://<webApiAppId>/access_as_user
, for the client-credentials flow you have to requestapi://<webApiAppId>/.default