Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] improve developer experience with login.microsoftonline.com in B2C tenants #143

Closed
3 of 5 tasks
prabh-62 opened this issue May 5, 2020 · 21 comments
Closed
3 of 5 tasks
Assignees
Milestone

Comments

@prabh-62
Copy link

prabh-62 commented May 5, 2020

What do do:

When B2C is detected (presence of a policy) but the authority is login.microsoftonline.com without the tfp, we should log an error and throw an exception ArgumentException in AddMicrosoftWebApp(), so that customers know that they either have to use login.b2c.com or have tfp.

Error message:

cc: @jennyf19
Would #168 be a duplicate?

Initial report:

Documentation Related To Component:

Microsoft.Identity.Web nuget package (version 0.1.1-preview)

Please check those that apply

  • typo
  • documentation doesn't exist
  • documentation needs clarification
  • error(s) in example
  • needs example

Description Of The Issue

  • > dotnet --version
    3.1.201
  • > git clone https://github.com/AzureAD/microsoft-identity-web.git
  • > cd microsoft-identity-web/ProjectTemplates
  • > dotnet pack AspNetCoreMicrosoftIdentityWebProjectTemplates.csproj
  • > cd bin/Debug
  • > dotnet new --install Microsoft.Identity.Web.ProjectTemplates.0.1.0.nupkg

Verify if the templates are available

  • > dotnet new

webapi2

Now, let's create a new ASP.NET Core web api project

  • Navigate to a folder where we will create new project
  • > dotnet new webapi2 --auth Singleorg -n WeatherStation
  • > cd WeatherStation
  • Update appsettings.json file (image has fake data)

app_settings

  • > dotnet run

  • > curl -i http://localhost:5000/weatherforecast -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI2NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNzNiOWUwZjgtNDY1Yy00MzY1LTkzNGUtMTM4ZmUxYjEyNDQ4L3YyLjAvIiwiZXhwIjoxNTg4NjQ2NTIyLCJuYmYi9wbWVudCIsImZhbWlseV9uYW1lIjoiR3JvdXAiLCJuYW1lIjoiRGV2ZWxvcG1lbnQgR3JvdXAiLCJuZXdVc3VyIjpmYWxzZSwiZXh0ZW5zaW9uX0NvbXBhbnkiOiJNSUNST0RFQSIsImV4dGVuc2lvbl9QaG9uZSI6IjU1NTU1NTU1NTUiLCJlbWFpbHMiOlsiZGV2ZWxvcG1lbnRncm91cEBtaWNyb2RlYS5jb20iXSwidGZwIjoiQjJDXzFfTG9hZFBhbERldiIsImF6cCI6IjliNzBjZTY4LTZlOWEtNGJiZC1iYWU3LWVhZGY5YjFkN2IxMSIsInZlciI6IjEuMCIsImlhdCI6MTU4ODYzOTMyMn0.hVAD5SZ4hIsyYdZgKPT0LBzHP3Ud5aU8vHXWQBcCMMOcjb5ZApiZSjzI7fdEQHuesudQtgwZumui-1a_XIV6v6jls5I_SlCr-h5bKJwa1VAW7_oKmKVxEjqt60dVJU8LIizySXimNXpS8W-YUHz0HBptE1vHndwadOT2OvB2ZOOHhNUnpNBdxaCYR-0TdSeH2ZnpXs6mphzxyRdD8-Bt7BB4FJZUNH63HpsJ3cV7aO08FrJ0jkveIdwcFy2WZbW-i1B8NWaWgPOpyx3DTWm3UCfJsLmVy21d6sK8LBL-vRaBfiSIfR9I1L2W_hB9U-TQMaTwQkAuXh4cNmg2u7GT8P"

signature_keys_not_found

I get an error message
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"

@prabh-62 prabh-62 added the documentation Improvements or additions to documentation label May 5, 2020
@prabh-62
Copy link
Author

prabh-62 commented May 5, 2020

In another project, I am able to retrieve list of users using GraphServiceClient
graphServiceAccount

Nugets in project:

    <PackageReference Include="Microsoft.Graph" Version="3.4.0" />
    <PackageReference Include="Microsoft.Graph.Auth" Version="1.0.0-preview.4" />
    <PackageReference Include="Microsoft.Identity.Client" Version="4.12.0" />
    <PackageReference Include="Microsoft.Identity.Web" Version="0.1.1-preview" />

@jmprieur
Copy link
Collaborator

jmprieur commented May 5, 2020

@prabh-62
Copy link
Author

prabh-62 commented May 5, 2020

Yes, I looked through multiple projects in that git repository. I tried the same approach as 4-WebApp-your-API/4-1-MyOrg.
startup_protected_web_api
authorize

I still get the same error.

HTTP/1.1 401 Unauthorized
Date: Tue, 05 May 2020 19:08:39 GMT
Server: Kestrel
Content-Length: 0
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"

signature_key

Approach 2-1-Call-MSGraph uses Microsoft.Identity.Web.UI and I am building an API so I cannot use that example.

@jmprieur
Copy link
Collaborator

jmprieur commented May 5, 2020

IF you are building a Web API you need to look at https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2
Would your API require JWE? (encrypted tokens) ?

@prabh-62
Copy link
Author

prabh-62 commented May 5, 2020

Access tokens in the API are normal JWTs.
I copied the exact approach from 3.-Web-api-call-Microsoft-graph-for-personal-accounts

			services.AddProtectedWebApi(Configuration)
                    .AddProtectedWebApiCallsProtectedWebApi(Configuration)
                    .AddInMemoryTokenCaches();

signature_key_not_found

@jmprieur
Copy link
Collaborator

jmprieur commented May 5, 2020

@prabh-62
Copy link
Author

prabh-62 commented May 5, 2020

These are the steps I followed

  • > git clone https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2.git
  • > cd active-directory-dotnet-native-aspnetcore-v2/2.\ Web\ API\ now\ calls\ Microsoft\ Graph/TodoListService/
  • Update credentials in appsettings.json
  • > dotnet run
  • Make a post call

curl_post_call

- I see exceptions

PII_signature_key

@prabh-62
Copy link
Author

prabh-62 commented May 8, 2020

I was able to get the AzureAdB2C Authentication working with the ASP.NET Core.

 public void ConfigureServices(IServiceCollection services)
        {
            var discoveryPoint = "https://login.microsoftonline.com/{DirectoryID}/v2.0/.well-known/openid-configuration?p={Policy}";
            var configManager =
                    new ConfigurationManager<OpenIdConnectConfiguration>(
                        discoveryPoint,
                        new OpenIdConnectConfigurationRetriever()
                    );

            var config = configManager.GetConfigurationAsync(CancellationToken.None).GetAwaiter().GetResult();

            services.AddProtectedWebApi(options =>
            {
                Configuration.Bind("AzureAdB2C", options);
                options.IncludeErrorDetails = true;

                options.TokenValidationParameters = new TokenValidationParameters
                {
                    RequireSignedTokens = true,
                    ValidIssuer = "https://login.microsoftonline.com/{DirectoryId}/v2.0/",
                    ValidAudience = "{ClientId}",
                    ValidateAudience = true,
                    ValidateIssuer = true,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKeys = config.SigningKeys,
                    ValidateLifetime = true
                };

                options.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                    {
                        context.Response.OnStarting(async () =>
                        {
                            context.NoResult();
                            context.Response.ContentType = "application/json";
                            byte[] bytes = Encoding.ASCII.GetBytes(context.Exception.Message);
                            await context.Response.Body.WriteAsync(bytes);
                            context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                        });

                        return Task.CompletedTask;
                    }
                };
            }, options => Configuration.Bind("AzureAdB2C", options));
        }

However, there is one small hurdle. I had to comment certain code in file src/Microsoft.Identity.Web/WebApiAuthenticationBuilderExtensions.cs of Microsoft.Identity.Web library

                options.Events.OnTokenValidated = async context =>
                {
                    // This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned.
                    // if (!context.Principal.Claims.Any(x => x.Type == ClaimConstants.Scope)
                    // && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Scp)
                    // && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles)
                    // && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Role))
                    // {
                    //     throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token.");
                    // }

                    await tokenValidatedHandler(context).ConfigureAwait(false);
                };

Exact Line link: https://github.com/AzureAD/microsoft-identity-web/blob/master/src/Microsoft.Identity.Web/WebApiAuthenticationBuilderExtensions.cs#L132
Is it possible to not throw exceptions in certain situations?

@jmprieur
Copy link
Collaborator

jmprieur commented May 11, 2020

Oh I see, @prabh-62 : you were trying to use B2C. the issue is that you have used

dotnet new webapi2 --auth Singleorg -n WeatherStation

and you should really use

dotnet new webapi2 --auth IndividualB2C-n WeatherStation

You shouldn't need to write all the code that you have written. Using just MIcrosoft.Identity.Web should work?
cc: @jennyf19

@jmprieur jmprieur added answered question Further information is requested labels May 11, 2020
@prabh-62
Copy link
Author

prabh-62 commented May 11, 2020

This morning, I tried again

  • > dotnet new webapi2 --auth IndividualB2C -n WeatherStation
  • Updated AzureAdB2C section in appsettings.json
  • Disabled HTTPS redirection
     	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
         {
             if (env.IsDevelopment())
             {
                 app.UseDeveloperExceptionPage();
                 IdentityModelEventSource.ShowPII = true;
             }
    
             // app.UseHttpsRedirection();
  • curl -i http://localhost:5000/weatherforecast -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNzNiOWUwZjgtNDY1Yy00MzY1LTkzNGUtMTM4ZmUxYjEyNDQ4L3YyLjAvIiwiZXhwIjoxNTg5MjEwMzcyLCJuYmYiOjE1ODkyMDMxNzIsImF1ZCI6IjliNzBjZTY4LTZlOWEtNGJiZC1iYWU3LWVhZGY5YjFkN2IxMSIsImlkcCI6IkxvY2FsQWNjb3VudCIsIm9pZCI6ImFjMWMwYzJmLWJiNjgtNDU1YS1hMTcxLTdiMjhjNDBlNTQwZCIsInN1YiI6ImFjMWMwYzJmLWJiNjgtNDU1YS1hMTcxLTdiMjhjNDBlNTQwZCIsImdpdmVuX25hbWUiOiJEZXZlbG9wbWVudCIsImZhbWlseV9uYW1lIjoiR3JvdXAiLCJuYW1lIjoiRGV2ZWxvcG1lbnQgR3JvdXAiLCJuZXdVc2VyIjpmYWxzZSwiZXh0ZW5zaW9uX0NvbXBhbnkiOiJNSUNST0RFQSIsImV4dGVuc2lvbl9QaG9uZSI6IjU1NTU1NTU1NTUiLCJlbWFpbHMiOlsiZGV2ZWxvcG1lbnRncm91cEBtaWNyb2RlYS5jb20iXSwidGZwIjoiQjJDXzFfTG9hZFBhbERldiIsImF6cCI6IjliNzBjZTY4LTZlOWEtNGJiZC1iYWU3LWVhZGY5YjFkN2IxMSIsInZlciI6IjEuMCIsImlhdCI6MTU4OTIwMzE3Mn0.JIQkmQ_3MTwpt3WheWERdY7YEDx48Yef3kitiUW-2tnThOACqblWGxBSek8HarAyvahlVr6jnENuipvW_px6nJXu0EWZMJTDQ9wkeIPdVNlbws3iwvh2hctTFUAztVWo_noSdFRqeb_U95sTLsCTEu3ck4mLBYOxuCsHhKlyeui_8n6R57J4-B0Jjk_BUsadsS_0GHKHJeAaJ14M2QEtertUZIgQQIqFb07OwrB_PIvyTZxaofPAwKEkvWwwMtns-xjCpQNlje41py0eUl6cfDjIUpOWyl2alk5ASjrqaZK2sYx3JDkSUDun33UOh9VIcog_JBQ7HVREF97bCVnp7w"

And I get an exception
pII_error

https://login.microsoftonline.com/{DirectoryID}/v2.0/.well-known/openid-configuration - I can confirm, this URL does return OpenID Configuration

I am not sure why SignUpSignInPolicyId: B2C_1_SignupSignin is included in the URL

@jennyf19
Copy link
Collaborator

@prabh-62 What is the instance and domain value in appsettings.json for the b2c tenant?

@prabh-62
Copy link
Author

This is how my appsettings.json looks like

{
  "AzureAdB2C": {
    "Instance": "https://login.microsoftonline.com",
    "ClientId": "Guid",
    "B2cAppId": "Guid",
    "DirectoryId": "Guid",
    "Domain": "MicrodeaDev.onmicrosoft.com",
    "SignUpSignInPolicyId": "B2C_1_SignupSignin"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

@jennyf19
Copy link
Collaborator

@prabh-62 thank you.
If you're using B2C, you have to include the policy in order to get the correct metadata and endpoints.

So, if you're using login.microsoftonline.com + policy (which is being deprecated as a b2c endpoint), you need to include the policy:
https://login.microsoftonline.com/tfp/{domain}/{policy}/v2.0/.well-known/openid-configuration

Did you create an app in your B2C tenant? You cannot mix the AzureAd settings and B2C settings, they are separate authorization servers at the moment, if i'm understanding your scenario correctly.

You should also be using *.b2clogin.com now as the tenant. Let me know if this helps.

@prabh-62
Copy link
Author

prabh-62 commented May 11, 2020

Thank you for helping me troubleshoot. We will be migrating to *.b2clogin.com soon. I updated the appsettings.json

{
    "AzureAdB2C": {
      "Instance": "https://login.microsoftonline.com/tfp/",
      "ClientId": "Guid",
      "B2cAppId": "Guid",
      "DirectoryId": "Guid",
      "Domain": "MicrodeaDev.onmicrosoft.com",
      "SignUpSignInPolicyId": "B2C_1_SignupSignin"
    },
    "Logging": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    },
    "AllowedHosts": "*"
  }

Also, I had to comment out the following lines in the library
scope_check_identity

The authentication is working now with the following startup code.
startup_code

🤗

@jennyf19
Copy link
Collaborator

@prabh-62 thanks for the quick reply.

@jmprieur how would you like to proceed w/handling this use case (b2c w/login.microsoftonline.com)?

@jmprieur
Copy link
Collaborator

Thanks @jennyf19
Do we want to throw in Microsoft.Identity.Web if we detect B2C (a user flow) and authority is login.microsoftonline.com without the tfp ? We could have a meaningful exception advising to use b2cloging.com? what do you think?

@bgavrilMS
Copy link
Member

#supportability

@jennyf19
Copy link
Collaborator

@jmprieur let's discuss.

@jennyf19 jennyf19 changed the title [Documentation] ASP.NET Core 3.1 Error: invalid_token = The signature key was not found [Feature Request] improve developer experience with login.microsoftonline.com in B2C tenants May 15, 2020
@jennyf19 jennyf19 added the b2c label May 15, 2020
@jennyf19
Copy link
Collaborator

@prabh-62 i updated the title, as i believe this was the root cause. will make it easier for us to track. hope you don't mind. :)

@jennyf19 jennyf19 added this to the 0.1.4-preview milestone May 15, 2020
@jmprieur jmprieur added the enhancement New feature or request label May 15, 2020
@jennyf19 jennyf19 modified the milestones: 0.1.4-preview, 0.1.6-preview May 22, 2020
@jmprieur jmprieur removed the answered label Jun 22, 2020
@jmprieur jmprieur removed the question Further information is requested label Jun 22, 2020
@jmprieur jmprieur modified the milestones: 0.2.1-preview, [4] Custom B2C policies out of the box support Jun 26, 2020
@jmprieur
Copy link
Collaborator

jmprieur commented Nov 26, 2020

One of the issues is for a b2C web app that calls a web API, if the developer provides the tfp in the authority in the appsettings.json, the sign-in part of the Oauth code flow works, but Microsoft.Identity.Web adds a second /tfp in the authority, and MSAL fails.

{
  "AzureAdB2C": {
    "Instance": "https://login.microsoftonline.com/tfp/",

Possible work around for this case (and only this case) would be to add the following line after

var authority = $"{_applicationOptions.Instance}{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{userFlow ?? _microsoftIdentityOptions.DefaultUserFlow}";

   // Consider the case where the authority in the config ends with /tfp
   authority = authority.Replace("/tfp/tfp", "/tfp");

This is an issue because the ASP.NET Core templates do that.
Proposal:

  1. apply this work around to Microsoft.Identity.Web (See Removes a double /tfp in B2C web apps calling web APIs when instance ends in /tfp/ #789). This is a short term work around
  2. Have the ASP.NET Core templates changed to use b2clogin.com instead of https://login.microsoftonline.com/tfp/

@jennyf19 jennyf19 added the fixed label Dec 1, 2020
@jmprieur jmprieur removed the documentation Improvements or additions to documentation label Dec 6, 2020
@jennyf19 jennyf19 self-assigned this Dec 6, 2020
@jennyf19
Copy link
Collaborator

jennyf19 commented Dec 9, 2020

Included in 1.4 Release.

@jennyf19 jennyf19 closed this as completed Dec 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants