5
votes

So I raised this question Azure AD Multi Tenant ,.Net Core Web API with MSAL(Microsoft Authentication Libary) which showed me how to turn on the error output.

Why would I be getting this now with my token, at a guess is the aud right and the iss for a multi tenant ad token?

AuthenticationFailed: IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey, KeyId: 'YMELHT0gvb0mxoSDoYfomjqfjYU', InternalId: '2c34a300-21bb-4eb1-b3b9-1944f1be7470'. , KeyId: YMELHT0gvb0mxoSDoYfomjqfjYU
'. 
kid: 'YMELHT0gvb0mxoSDoYfomjqfjYU'. 
Exceptions caught:
 ''.
token: '
{
    "alg": "RS256",
    "typ": "JWT",
    "nonce": "bWqlNum32nkLGFA4s5lE83AEZ6hRUzqi4r4-3JMZLdw",
    "x5t": "YMELHT0gvb0mxoSDoYfomjqfjYU",
    "kid": "YMELHT0gvb0mxoSDoYfomjqfjYU"
}
.{
    "aud": "00000003-0000-0000-c000-000000000000",
    "iss": "https://sts.windows.net/abc60396-1ed0-4fa3-a3d0-597adf1366a5/",
    "iat": 1584438416,
    "nbf": 1584438416,
    "exp": 1584442316,
    "acct": 0,
    "acr": "1",
    "aio": "42NgYNh7TvzDvOILBsl/7E+U+vxP6y5rmJERnny04o5ZM2vJjmwA",
    "amr": ["pwd"],
    "app_displayname": "AzureAdTest",
    "appid": "A134d6c8-8078-2924-9e90-98cef862eb9a",
    "appidacr": "0",
    "family_name": "Bob",
    "given_name": "Bob",
    "ipaddr": "111.111.124.18",
    "name": "Bob Powell",
    "oid": "5b2dfaea-41fb-4a76-93da-6b4c04041f4d",
    "platf": "3",
    "puid": "10032000A35A0EE1",
    "scp": "openid profile User.Read email",
    "sub": "NM4nVqUfyC-6pF66I1Wef8Bvl7rhnpB_UBv7fX-qMHU",
    "tid": "abc60396-1ed0-4fa3-a3d0-597adf1366a5",
    "unique_name": "[email protected]",
    "upn": "[email protected]",
    "uti": "-mwXtFoS1kGJjorQqzI0AA",
    "ver": "1.0",
    "xms_st": {
        "sub": "p7nf6_rRkoqINUHy3cl_qRQ2F-DaCfFwQgy6gTQv_QY"
    },
    "xms_tcdt": 1583932579
}
'.

My previous question was:

I believe I have the Microsoft Authentication Library (MSAL) JavaScript pulling back a JWT token, using azure AD multi tenant with the following config. Based of this link https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant. I believe I only need the following two values.

clientId: "A134d6c8-8078-2924-9e90-98cef862eb9a" // this would be the app registrations client id(application)
authority: "https://login.microsoftonline.com/common"

How then can I configure a .net core 3 web api that can handle this JWT token and authenticate [Authorize] endpoints by me passing the Authorization: Bearer header.

I currently get this error in the response which is not very helpful!

AuthenticationFailed: IDX10511: Signature validation failed. Keys tried: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. 
kid: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. 
Exceptions caught:
 '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
token: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.

The Startup.cs code is as follows

using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;

namespace MultiTenantApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {

            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(x =>
            {
                x.AddDefaultPolicy(cfg =>
                {
                    cfg.AllowAnyOrigin()
                        .AllowAnyHeader()
                        .AllowAnyMethod();
                });
            });

            services.AddAuthentication(cfg =>
                {
                    cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(opt =>
                {
                    opt.Authority = "https://login.microsoftonline.com/common";
                    opt.Audience = "api://A134d6c8-8078-2924-9e90-98cef862eb9a"; // Set this to the App ID URL for the web API, which you created when you registered the web API with Azure AD.
                    opt.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = false
                    };
                    opt.Events = new JwtBearerEvents()
                    {
                        OnAuthenticationFailed = AuthenticationFailed
                    };
                });

            services.AddControllers();

        }

        private Task AuthenticationFailed(AuthenticationFailedContext arg)
        {
            // For debugging purposes only!
            var s = $"AuthenticationFailed: {arg.Exception.Message}";
            arg.Response.ContentLength = s.Length;
            arg.Response.Body.WriteAsync(Encoding.UTF8.GetBytes(s), 0, s.Length);
            return Task.FromResult(0);
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseStaticFiles(); // Added

            app.UseRouting();
            app.UseCors(); //Added

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

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

        }

    }
}
2
The use of the OnAuthenticationFailed was a life saver thank you, lead me directly towards the issue.Hawkzey

2 Answers

6
votes

With the help of juunas response above, I change my scope from user.read to the following this is my client id(application id) followed by .default

var tokenRequest = {
    scopes: ["A134d6c8-8078-2924-9e90-98cef862eb9a/.default"]
};
await this.app.acquireTokenSilent(tokenRequest)
   ... etc

After this I could see aud value was no longer the graph api one

"aud": "A134d6c8-8078-2924-9e90-98cef862eb9"

In the C# API my code now works with this in the ConfigureServices Startup.cs file

services.AddAuthentication(cfg =>
{
    cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
    opt.Authority = "https://login.microsoftonline.com/common";
    opt.Audience = "api://A134d6c8-8078-2924-9e90-98cef862eb9a"; // Set this to the App ID URL for the web API, which you created when you registered the web API with Azure AD.

    opt.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true, 
        ValidateAudience = true, 
        ValidAudiences = new List<String>
        {
            // you could add a list of valid audiences
            "A134d6c8-8078-2924-9e90-98cef862eb9a"
        }, 
        ValidIssuers = new List<string>
        {
            // Add tenant id after https://sts.windows.net/
            "https://sts.windows.net/{YourTenantId}"
        }
    };
    opt.Events = new JwtBearerEvents()
    {
        OnAuthenticationFailed = AuthenticationFailed
    };
});
2
votes

Looks like you are trying to validate a token meant for MS Graph API. These tokens are special and you should not attempt to validate them. You should always only accept tokens meant for your API, not other APIs.

The reason I know that is the case is because of this:

"aud": "00000003-0000-0000-c000-000000000000"

That's the audience value for Microsoft Graph API.

For a token meant for your API and for you to validate, this should be the client id or app ID URI of your API.

When you acquire an access token in the front-end, use the scope your-api-client-id/.default for statically assigned permissions, or a dynamic set of scopes like ['your-api-client-id/scope-a', 'your-api-client-id/scope-b']. These scopes are defined on your app registration.