0

I am trying to acquire access token on behalf of a user(client application). So I am generating an accesss token for a client application which it be using to access middle tier service. The middle tier service will be accessing the downstream service. Basically the client needs to access the downstream service but it can't use the service directly, so middle tier service will be accessing on behalf of the client. I am getting the following error :

error message: 'AADSTS70000: The request was denied because one or more scopes requested are unauthorized or expired. The user must first sign in and grant the client application access to the requested scope.

The code I have written is :

string[] targetScopes = new string[] { "api://xxxx-xxxx-xxxx-xxxx/Downstreamservice.ReadWrite" };

            UserAssertion userAssertion = new UserAssertion(middleTierServiceAccessToken);
            string clientId = configuration["Auth:ClientId"];
            string redirectUri = url;
            string authority = string.Format(CultureInfo.InvariantCulture, configuration["Auth:AzureAdInstance"], configuration["Auth:Tenant"]);

            string clientSecret = "xxx-xxxx-xxxx";

            // Initialize the confidential client application
            IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
                .Create(clientId)
                .WithRedirectUri(redirectUri)
                .WithAuthority(new Uri(authority))
                .WithClientSecret(clientSecret)
                .Build();

// Exception with the above error is getting thrown in the line below            
AuthenticationResult result = await app.AcquireTokenOnBehalfOf(targetScopes, userAssertion).ExecuteAsync(); 
            string downStreamServiceAccessToken = result.AccessToken;

The middleTierServiceAccessToken is being fetched correctly.Do i need to add any scope on the client side ?

I have checked repeatedly the targetscope url and if the client id of my middletierService is added as authorized client application. But I am still getting the error. I have tried switching the cases of the scope to downstreamservice.readwrite but that did not work either.

i can't figure out the reason why this is happening.

MChak
  • 25
  • 6
  • Could you please confirm whether you granted `api://xxxx-xxxx-xxxx-xxxx/Downstreamservice.ReadWrite` API permission in Client Application? – Rukmini May 04 '23 at 06:44
  • @Rukmini can you rephrase? I am using OBO flow, so why will it be necessary for client application to request permisssion for downstream service? Wont that defeat the entire purpose of implementing OBO flow? – MChak May 04 '23 at 06:48
  • have you tried to use the `downStreamServiceAccessToken` you get from your code to call your downstream service via tools like postman? I just want to confirm if the token you generated is correct or not. – Tiny Wang May 04 '23 at 09:11
  • by the way if you don't mind to create a new middleware sample, you may take a look at my [this answer](https://stackoverflow.com/a/76015088/14574199)... and about what Rukmini mentioned `API permission in Client Application`, I'm afraid if you already give admin consent to `api://xxxx-xxxx-xxxx-xxxx/Downstreamservice.ReadWrite`, it won't influence the on-behalf-of flow. – Tiny Wang May 04 '23 at 09:15
  • @TinyWang the exception is getting thrown at app.AcquireTokenOnBehalfOf. So I am not able to generate the downSteamServiceAccessToken. The error is being thrown because apparently my middle tier service is not authorized to access downstreamservice. Hence the access token won't be generated. This is what I believe is happening. I have edited the question to pinpoint where the exception is getting thrown. – MChak May 04 '23 at 10:13
  • hmmm, could you pls take a try with my code? for a better view I posted as an answer.. – Tiny Wang May 04 '23 at 10:34
  • I've updated my post and you may have a test to see if the token you used in on_behalf_flow is correct or not. – Tiny Wang May 05 '23 at 06:09

1 Answers1

0

Using On_behalf_of flow means we need the API1 to call API2, so the access token1 we used to generate another token2 to call API2 should have scp: api://xxx/scopeName, then the token we used to call API2 may also value like scp:api://xx/scopeName if API2 is a custom API, and may have value like scp: User.Read.All Group.Read.All if API2 is Ms Graph API. I also test to generate token for API1 with Graph API scope, I can get token1, but I can't use token1 to generate token2.

We need to make sure: All the API permissions we used in the request have been consented. Then the scope we used for generating token1 is like api://xxx/scopeName which means the token is for delegated API permission and is for custom Web Api.

My test steps:

  1. get auth code for generate token1, the scope I set in the request is user.read, it doesn't matter.
https://login.microsoftonline.com/tenant_id/oauth2/v2.0/authorize?  
client_id=client_id  
&response_type=code  
&redirect_uri=http://localhost/myapp/ 
&response_mode=query  
&scope=User.Read
&state=12345
  1. using the code get from the browser url query to generate token1. enter image description here enter image description here
Post: https://login.microsoftonline.com/tenant_id/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id=aad_client_id
&redirect_uri=http://localhost/myapp/
&grant_type=authorization_code
&client_secret=client_secret
&code=xxx
&scope=api://aad_client_id_of_exposing_api/scopeName
  1. test if the token1 is correct by use it in on_behalf_of request:
Post: https://login.microsoftonline.com/tenant_id/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
client_id=xxx
&client_secret=xxx
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&assertion=token1_here
&scope=user.read
&requested_token_use=on_behalf_of

enter image description here

Following this document and I have a test with code below.

using Azure.Core;
using Azure.Identity;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Graph;

StringValues authorizationToken;
HttpContext.Request.Headers.TryGetValue("Authorization", out authorizationToken);
var token = authorizationToken.ToString().Replace("Bearer ","");
var scopes = new[] { "User.Read.All" };//this is for ms graph api, you can change it to your custom api://xxx/scope_name
var tenantId = "xxxx";
var clientId = "xxxx";
var clientSecret = "xxxx";
var onBehalfOfCredential = new OnBehalfOfCredential(tenantId, clientId, clientSecret, token);
var tokenRequestContext = new TokenRequestContext(scopes);
var token2 = onBehalfOfCredential.GetTokenAsync(tokenRequestContext, new CancellationToken()).Result.Token;
Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
  • Hey @TinyWang , I am getting the same error even with your code. – MChak May 05 '23 at 05:37
  • could you pls show us the decode result of your token? I mean `authorizationToken` decoded in jwt.io, I want to see the `scp` claim. – Tiny Wang May 05 '23 at 05:39
  • I decoded middleTierServiceAccessToken , it has the following scope : "scp": "middletier.read", – MChak May 05 '23 at 07:48
  • Can you rephrase the first paragraph of your answer a bit. I have set the scope to access MiddleTierService as middletier.read in middleTierServiceAccessToken and the scope to access DownStreamService as Downstreamservice.ReadWrite. Do I need to add Downstreamservice.ReadWrite scope as one of the target scopes for middleTierServiceAccessToken? But won't that defeat the purpose of on-behalf-of flow? – MChak May 05 '23 at 07:55
  • @MChak I already updated with all the step steps about generating access token using on behalf of flow, let's make sure we can generate the token successfully first then go back to the code. – Tiny Wang May 05 '23 at 08:42