Validating token signatures in ASP.Net Core


#1

I have a mobile app built with ionic 3. I have a web api built with ASP.Net Core. I am trying to authenticate users against our Azure Active Directory tenant, but have been unsuccessful so far. I am using cordova-plugin-ms-adal and @ionic-native/ms-adal.

I have two apps registered with AAD (MobileApp as a native app and WebAPI as a web app/api). I am able to authenticate with AAD using the following code:

      let context = this.msAdal.createAuthenticationContext("https://login.microsoftonline.com/common");
      context.acquireTokenAsync(parent.config.resourceUri, parent.config.clientId, parent.config.redirectUri, userID, parent.config.queryParams)
              .then(authCompletedCallback)
              .catch((e: any) => console.log('Authentication failed', e));
}

This successfully brings up the microsoft AAD login screen, and then I get a token back, which I can translate using jwt.io to look like this:

{
  "aud": "https://crm.advtis.com/",
  "iss": "https://sts.windows.net/[my-tentant-id]/",
  "iat": 1507559003,
  "nbf": 1507559003,
  "exp": 1507562903,
  "acr": "1",
  "aio": "ASQA2/8FAAAATCdl0Upc7HOvqfvEXmDy7geoc3GHx5e+jwxtbvvN8+I=",
  "amr": [
    "pwd"
  ],
  "appid": [my-app-id],
  "appidacr": "0",
  "e_exp": 262800,
  "family_name": "Walter",
  "given_name": "Philip",
  "ipaddr":[address],
  "name": "Philip Walter",
  "oid": "49838dcd-e6af-4f21-9b50-d9bc434dee9d",
  "onprem_sid": "S-1-5-21-2694580895-890097088-1073055783-4610",
  "scp": "user_impersonation",
  "sub": "bOZ4xeO5pjXbg2hLnZAJzVVX_HfYZv6fjtQq0YaEKts",
  "tid": [our-tenant-id],
  "unique_name": "pwalter@advtis.com",
  "upn": "pwalter@advtis.com",
  "ver": "1.0"
}

So when I turn around and send this token over to the web api, I get a 401 Unauthorized error, saying WWW-Authenticate: Bearer error="invalid_token", error_description="The signature is invalid".

I have tried several things over multiple days turning into more than a week now. I know this isn’t an ASP.Net forum, but I thought someone might have some experience with this. I can provide more info like code from the web api as necessary. Any help would be appreciated!

Philip


#2

Can you get it to work with postman?


#3

No. Both Postman and Fiddler return the 401 - Bearer error="invalid_token", error_description="The signature is invalid". I suspect this has something to do with the signature credentials and the application manifests in AAD. I have been trying to use the following walkthroughs today, but have made very little progress:

It’s also probably worth noting that when I put the token into jwt.io, it says the signature is invalid, but I frankly have no idea what key to put into it to test the validation.

Thanks for any further ideas!


#4

I have the same archutecture but I’m thinking because they have different tennant they are going to be seen as different applications. You could call the web api to login and get the access token back and sent that over in the header for every call to protected api. In the app you can store that and guard your routes if there is no token set. But then you would want to renew it because that only lasts 1 hour. So maybe check the expiration on the local storage to see if it hasn’t expired and then on the api calls check again to see if it is close to needing a refresh. My other problem was getting it to refresh in the api. I could not get that working.


#5

They are different apps, but they are still on the same tenant, so I’m not sure what impact that will have. Also, I’m not calling the API to login, I’m going to AAD and logging in using the client application’s clientID. The client application has permissions to access the API, and I get a token back from AAD. Therefore, I thought the token would get through my API, but it is unable to verify the signature. I thought the whole idea of using AAD was so my web API never had to deal with credentials, just validate a token and serve up a resource. I have seen multiple articles using a client app with permissions to a web api as a pattern, but this is proving more difficult to figure out than anticipated.

Are you able to get a token from AAD and gain access to a web api resource with the resulting token, all refresh issues aside?


#6

When I try to authenticate from the app itself, the app stops working.

I tried with MSAdal. AzureB2C is just a const like you parent.config. I’ve passed in the empty string as resourceUrl and userId is null. But in the extraQueryParams, I’ve added p=policy_for_signup_from_api. In the Azure Portal, I’ve given my app permissions to the scopes defined for the api. Maybe this last one is your issue. But my app just crashes. I think I need to pass scope as well as extraQueryParams. Can you tell me if you’re doing it differently.

let authContext: AuthenticationContext = this.msAdal.createAuthenticationContext(AzureB2C.authority);

// Shows user authentication dialog if required
    authContext.acquireTokenAsync(AzureB2C.resourceUrl, AzureB2C.clientId, AzureB2C.redirectUrl, AzureB2C.userId, AzureB2C.extraQueryParams)

The version of ms-adal library and ionic native plugin I have are

  "cordova-plugin-ms-adal": "^0.10.1",
  "@ionic-native/ms-adal": "^4.3.1",

#7

That’s pretty much exactly what I’ve done. I’ve tried a few different extraQueryParams, mostly trying to include client_secret in order to get the signature to validate. I don’t have any issues in the client app. The login process goes as expected, and the token comes back, but like I said, when I request a resource from the API, it comes back saying the signature is invalid.

Here is my ms-adal library and plugin info:

"cordova-plugin-ms-adal": "^0.10.1", "@ionic-native/ms-adal": "^4.2.1"

What does the authentication setup in your API look like? Mine looks like this:

public void ConfigureServices(IServiceCollection services)
        {

            services.AddDbContext<AdvancedDBContext>();
            services.AddAuthentication();
            services.AddCors();
            services.AddMvc();
        }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseCors(builder =>
            {
                builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
            });

            app.UseJwtBearerAuthentication(new JwtBearerOptions
            {

                AutomaticAuthenticate = true,
                AutomaticChallenge = true,
                Authority = Configuration["Authentication: AzureAd:AADInstance"] + Configuration["Authentication: AzureAd:Tenant"],
                Audience = Configuration["Authentication: AzureAd:Audience"],
                TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = "https://sts.windows.net/[tenantId]/",

                    ValidateAudience = true,
                    ValidAudience = "https://crm.advtis.com/",

                }

            });

            app.UseMvc();

        }
    }

Then I just use the [Authorize] attribute on any routes I want to protect.


#8

This is my dot net core 2.0 web api code.

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.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
             .AddJwtBearer(options =>
            {
                options.Authority = string.Format("https://login.microsoftonline.com/tfp/{0}/{1}/v2.0/",
                    Configuration["Authentication:AzureAd:Tenant"], Configuration["Authentication:AzureAd:Policy"]);

                options.Audience = Configuration["Authentication:AzureAd:ClientId"];
                options.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = AuthenticationFailed
                };

                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    NameClaimType = "name"                    
                };

            });

          
            ScopeRead = Configuration["Authentication:AzureAd:ScopeRead"];
            ScopeWrite = Configuration["Authentication:AzureAd:ScopeWrite"];
                     services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                // For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
            }

            app.UseSession();

            app.UseAuthentication();

            app.UseMvc(routes =>
             {
                 routes.MapRoute(
                     name: "default",
                     template: "{controller=Home}/{action=Index}/{id?}");
             });


        }

     
        private Task AuthenticationFailed(AuthenticationFailedContext arg)
        {
            // For debugging purposes only!
            var s = $"AuthenticationFailed: {arg.Exception.Message}";
            arg.Response.ContentLength = s.Length;
            arg.Response.Body.Write(Encoding.UTF8.GetBytes(s), 0, s.Length);
            return Task.FromResult(0);
        }

Looks as though we’re using different Authority endpoint. What authority do you use on you client side and the resourceUrl and redirectUri? I’m struggling to get this correct. I also enabled twitter which I can get to with server side code.


#9

So, I am not using v2.0. My Authority endpoint is just https://login.microsoftonline.com/[my-tenant-id]. That gets me through the authentication flow with the token payload seen above. My resourceUrl is the same as the API resource I’m wanting to access: https://crm.advtis.com, which is also reflected in the token payload above. My redirectUri is the redirect URL I set up in AAD Portal for the client Mobile App. It doesn’t resolve, but I didn’t think it really needed to. All that info seems to jive with what I’ve read, but I’m still getting the 401 error.

I will change my setup to specify DefaultAuthenticateScheme and DefaultChallengeScheme when I do services.AddAuthentication(), since I did not have that in there before. Not sure what ScopeRead or ScopeWrite has on anything, but I have not included them up to this point either.

Thanks for your help so far. Really looking forward to getting this one resolved!


#10

Same here. Need to get this working.

I just got token back by setting to

  authority: 'https://login.microsoftonline.com/common',
 resourceUrl: "https://graph.windows.net"
  redirectUrl: [the_custom_url_for_the_app_],

Obviously, the authority is not correct. I’ve tried login.microsoftonline.com.

Did you add any meta tags to index.html?

The scopes I’m guess will be necessary in order to have permission. Perhaps sending “scope=full_url_scope” as part of extraQueryParams will do it on your end.


#11

I think I know the problem.
ms-adal (sounds like msal.js but built with adal.js) is not meant to work with B2C. I don’t know why but it does not like the url https://login.microsoftonline.com/tfp/tenant/policy/v2.0/... I keep getting sent to outlook login instead of being able to use the signup or sign in policy.

This is how I get token for the web api. By manually navigating to the url in the browser.

https://login.microsoftonline.com/[tennant]/oauth2/v2.0/authorize?p=[policy]&client_id=[client_id]&nonce=defaultNonce&redirect_uri=[redirect_uri]&scope=[socpes]&response_type=token&prompt=login

Unfortunately I can’t see a way to construct this using this library. So going to give this a try:

https://github.com/Azure-Samples/active-directory-b2c-javascript-angular2.4-spa

This confirms that it does not support B2C. https://github.com/AzureAD/azure-activedirectory-library-for-cordova/issues/85


#12

I came across this forum post while experiencing the exact same problem. Since I got my setup working I’d like to share my solution. It might help someone in the future…

So in my case I used the wrong parameters for the acquireTokenAsync function.

I ended up using the following parameters to get it work

let authContext: AuthenticationContext = this.msAdal.createAuthenticationContext(authority);
authContext.acquireTokenAsync(resourceUrl, clientId, redirectUrl, '', '')

For createAuthenticationContext I used the authority https://login.microsoftonline.com/{tenant-id} (where the tenant-id can be found on portal.azure.com (Azure Active Directory -> App registrations -> Endpoints -> GUID of any of the endpoints)

For acquireTokenAsync I used the following parameters:
resourceUrl: Application ID of the Web API app (Azure Active Directory – App registrations – select Web API App) (this ends up as the “aud” parameter in the decoded Bearer token)
clientId: Application ID of the Native app (Azure Active Directory – App registrations – select Native App)
redirectUrl: Redirect URI of the Native app (Azure Active Directory – App registrations – select Native App – Settings – Redirect URIs)

One point to mention is that you have to give the Native app the required permission to access the Web API (Azure Active Directory – App registrations – select Native App – Settings – Required permissions)

Hope it helps!


#13

For everone here trying to use the ‘https://graph.microsoft.net/’ resourceUri, it seems to be no longer valid. Use ‘https://graph.microsoft.com/’ instead and it will work!