Search code examples
wpfasp.net-web-apiazure-active-directoryjwt

Jwt bearer token audience invalid even though correct audience is in token


The flow is i have a local server app "demoServer" which has a corresponding app registration in Azure AD. The server app is written using ASP.NET core WEB API template. The demoServer will receive an access token from a client app and then authenticate it. For now, i am using a local wpf app for the client. Lets assume the wpf app is named demoClient. For the client also i have create an app registration.

Currently to test the flow, I have to follow the below steps: 1.I have to keep the server app running. 2. I will run the wpf app. It has a login button. When i click the login button, i am redirected to microsoft login page. 3. I do the login there. 4. I get the access token. 5. The token is sent to the server. 6. The server should authorize the token and send the response.

Currently everything is working fine till step 5. The issue is happening in server side getting 401 error. I tried to call an api in server as well using swagger where i used the access token i got in step 4 and copied and pasted in swagger authorization ui and i am getting the same 401 error :

 www-authenticate: Bearer error="invalid_token",error_description="The audience 'demoServer-applicationId-inAzureAAD' is invalid" 

I tried to decode the jwt token, there the aud claim is having the value : demoServer-applicationId-inAzureAAD.

Then why is it showing the value invalid?

For my demoServer the program.cs file contains the following code :

public class Program
    {

        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.Configure(app =>
                    {
                        app.UseSwagger();
                        app.UseSwaggerUI(c =>
                        {
                            c.SwaggerEndpoint("/swagger/v1/swagger.json", "weatherForecast");
                        });

                        if (app.ApplicationServices.GetService<IWebHostEnvironment>().IsDevelopment())
                        {
                            app.UseDeveloperExceptionPage();
                        }

                        app.UseRouting();

                        app.UseAuthentication();
                        app.UseAuthorization();

                        app.UseEndpoints(endpoints =>
                        {
                            endpoints.MapControllers();
                        });
                    });
                    
                    webBuilder.ConfigureServices(services =>
                    {
                        services.AddControllers();
                        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                            .AddJwtBearer(options =>
                            {
                                options.Authority = "https://login.microsoftonline.com/demoTenantId/v2.0";
                                options.Audience = "api://demoServer-applicationId-inAzureAAD";
                            
                            });
                        
                        services.AddAuthorization(options =>
                        {
                            options.DefaultPolicy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .Build();
                        });

                        services.AddSwaggerGen(c =>
                        {
                            c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });

                            var securityScheme = new OpenApiSecurityScheme
                            {
                                Name = "Authorization",
                                Description = "JWT Authorization header using the Bearer scheme (Example: 'Bearer 12345abcdef')",
                                In = ParameterLocation.Header,
                                Type = SecuritySchemeType.ApiKey,
                                Scheme = "Bearer",
                                BearerFormat = "JWT",
                                Reference = new OpenApiReference
                                {
                                    Type = ReferenceType.SecurityScheme,
                                    Id = "Bearer"
                                }
                            };

                            c.AddSecurityDefinition("Bearer", securityScheme);
                            c.AddSecurityRequirement(new OpenApiSecurityRequirement
                            {
                                { securityScheme, Array.Empty<string>() }
                            });
                        });

                    });
                });
    }

In demoServer app registration, i have already exposed an api : api://demoServer-applicationId-inAzureAAD/Files.Read

I have also added demoClient-applicationId-inAzureAAD in authorized client applications.

And yes, the Files.Read permission is already added in demoServer app registration API Permisssions.

The code below is for wpf app:

public partial class MainWindow : Window
        {
            
        public class TokenProvider
        {
            private readonly string _clientId;
            private readonly string _tenantId;
            private readonly string _redirectUri;

            public TokenProvider(string clientId, string tenantId, string redirectUri)
            {
                _clientId = clientId;
                _tenantId = tenantId;
                _redirectUri = redirectUri;
            }

            public async Task<string> GetAccessTokenAsync(string[] scopes)
            {
                IPublicClientApplication app = PublicClientApplicationBuilder
                    .Create(_clientId)
                    .WithTenantId(_tenantId)
                    .WithRedirectUri(_redirectUri)
                    .Build();

                AuthenticationResult result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
                return result.AccessToken;
            }
        }
        private async Task<string> CallApiWithTokenAsync(string accessToken)
        {
            using (HttpClient client = new HttpClient())
            {
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

                HttpResponseMessage response = await client.GetAsync("https://localhost-Url-For-ServerApp/Demo");

                if (response.IsSuccessStatusCode)
                {
                    return await response.Content.ReadAsStringAsync();
                }
                else
                {
                    throw new Exception($"API call failed with status code {response.StatusCode}");
                }
            }
        }

        private async void LoginButton_Click(object sender, RoutedEventArgs e)
        {
            string[] scopes = new string[] { "api://demoServer-applicationId-inAzureAAD/Files.Read", "api://demoServer-applicationId-inAzureAAD/user_impersonation" };

            TokenProvider tokenProvider = new TokenProvider("demoClient-applicationId-inAzureAAD",
                "TenantID",
                "https://localhost-Url-For-ServerApp");

            string accessToken = await tokenProvider.GetAccessTokenAsync(scopes);
            Debug.WriteLine(accessToken);

            // Send the access token to the server app for authentication.
            await CallApiWithTokenAsync (accessToken);
        }

        public MainWindow()
            {
                InitializeComponent();
            }                           
                        

        }

Sample token :

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "-XXXXXXXXXXXXXXXXXXXX"
}.{
  "aud": "demoServer-applicationId-inAzureAAD",
  "iss": "https://login.microsoftonline.com/tenantID/v2.0",
  "iat": ###########,
  "nbf": ###########,
  "exp": ###########,
  "aio": "######################################",
  "azp": "demoClient-applicationId-inAzureAAD",
  "azpacr": "0",
  "name": "My Name",
  "oid": "someID",
  "preferred_username": "myEmail",
  "rh": "######################.",
  "scp": "Demo Files.Read user_impersonation",
  "sub": "######################",
  "tid": "tenantID",
  "uti": "#######################",
  "ver": "2.0"
}.[Signature]

Solution

  • Your audience value here is wrong:

    options.Audience = "api://demoServer-applicationId-inAzureAAD";
                            
    

    Should be:

     options.Audience = "demoServer-applicationId-inAzureAAD";