Azure-Samples / active-directory-aspnetcore-webapp-openidconnect-v2

An ASP.NET Core Web App which lets sign-in users (including in your org, many orgs, orgs + personal accounts, sovereign clouds) and call Web APIs (including Microsoft Graph)
MIT License
1.37k stars 989 forks source link

Demo 4-WebApp-Your-API Application ID URI should be https pattern #336

Open franva opened 4 years ago

franva commented 4 years ago

Please provide us with the following information:

This issue is for a: (mark with an x)

- [ ] bug report -> please search issues before submitting
- [ ] feature request
- [x] documentation issue or request
- [ ] regression (a behavior that used to work and stopped in a new release)

The issue was found for the following scenario:

Please add an 'x' for the scenario(s) where you found an issue

  1. Web app that signs in users
    1. [ ] with a work and school account in your organization: 1-WebApp-OIDC/1-1-MyOrg
    2. [ ] with any work and school account: /1-WebApp-OIDC/1-2-AnyOrg
    3. [ ] with any work or school account or Microsoft personal account: 1-WebApp-OIDC/1-3-AnyOrgOrPersonal
    4. [ ] with users in National or sovereign clouds 1-WebApp-OIDC/1-4-Sovereign
    5. [ ] with B2C users 1-WebApp-OIDC/1-5-B2C
  2. Web app that calls Microsoft Graph
    1. [ ] Calling graph with the Microsoft Graph SDK: 2-WebApp-graph-user/2-1-Call-MSGraph
    2. [ ] With specific token caches: 2-WebApp-graph-user/2-2-TokenCache
    3. [ ] Calling Microsoft Graph in national clouds: 2-WebApp-graph-user/2-4-Sovereign-Call-MSGraph
  3. [ ] Web app calling several APIs 3-WebApp-multi-APIs
  4. [x] Web app calling your own Web API 4-WebApp-your-API
  5. Web app restricting users
    1. [ ] by Roles: 5-WebApp-AuthZ/5-1-Roles
    2. [ ] by Groups: 5-WebApp-AuthZ/5-2-Groups
  6. [ ] Deployment to Azure
  7. [ ] Other (please describe)

Repro-ing the issue

Repro steps

  1. On the Active Directory Dotnet Native Aspnetcore V2 Demo 3, section

7 Select the Expose an API section

It asks to

Change the Application ID URI to the https pattern, check AzureADandPersonalMicrosoftAccount restrictions, (https://{tenant-domain}/{app-name}) and select Save and Continue.

but on the demo Active Directory Aspnetcore Webapp OpenIdConnect V2 4-WebApp-Your-API, section

6 Select the Expose an API section

It asks for

accept the proposed Application ID URI (api://{clientId}) by selecting Save and Continue

Since they both use Web Api as backend service to serve frontend(one as WPF client, one as Webapp), the configuration for Application ID URI should be same.

Why do they have 2 inconsistent configurations?

I believe, this Application ID URI should be one of them: https or api.

TiagoBrenck commented 4 years ago

Hi @franva, it depends. Is your API configured to support AzureADandPersonalMicrosoftAccount? If yes, then the https pattern should be used, otherwise, you can use either of them since you dont have any restrictions. To check those restrictions, please follow this doc.

franva commented 4 years ago

hi @TiagoBrenck thanks for pointing out where the document is. However, after looked at that doc, it makes me more confused. Please look at the screenshot below: image

Yes, I need to support both org and personal accounts. According to your answer, the Application ID URI should be the https pattern. But the referenced document does not mention https or api:// at all.

One more thing to point out, I can see Microsoft has poured tremendous efforts on documentations, I really appreciate that as it makes our lives easier. But as the more and more documents written, the new documents and the old ones are co-existing and there are so many documents there which confused me a lot.

Take the Microsoft Identity Platform as an example, all I need to do is to fetch emails from user's Hotmail using OBO flow, and I have a separated API project and a Reactjs project. I searched days and nights and found heaps of documents and in the end, the Javascript demo project doesn't even work in Firefox, only working in Edge. so some of documents are lack of love.

TiagoBrenck commented 4 years ago

Hi @franva, I am sorry for causing even more confusion.

So, the doc statement: "urn:// schemes are not supported" is the restriction that affects api://<>. Then, you should use another pattern, like the https:<> one. We have this sample that is a WebAPI using OBO flow with personal accounts (hotmail, xbox live etc). It might be worthy exploring it.

About the documentation feedback, I will forward it to the docs team. Could you share the Javascript demo link for me please?

franva commented 4 years ago

Thanks @TiagoBrenck I am still trying to find out an up-to-date example which fits my requirements. Once again, I found this one On point 10, it still shows to use the api:// pattern, even Point 3 has already selected the AnyOrg+Personal Accounts.

There are so many documents, but none of them works for me. I'm lost in the sea of out-of-date documents.

What I want is Reactjs + AspNet Core WebApi to fetch user's emails and just found this repo: https://github.com/Azure-Samples/ms-identity-javascript-spa-aspnetcore-webapi-obo/issues/1

But it's empty.

Please help.

TiagoBrenck commented 4 years ago

Hi @franva

I would like to apologize for a confusion about the MSA and api:// pattern. Due to a miscommunication, we told you that api:// is not supported for MSA however it is. We will update the sample to remove this miss information. It was actually a bug but it got fixed already.

Could you let me know if you manged to have your MSA scenario working?

Thanks

franva commented 4 years ago

thanks @TiagoBrenck for the update.

Unfortunately, no. I have been stuck here for about 4 weeks.

I have put my question here: https://stackoverflow.com/questions/61524182/msal-net-no-account-or-login-hint-was-passed-to-the-acquiretokensilent-call

If you could have a look, that would be great.

Thanks

TiagoBrenck commented 4 years ago

Your scenario is on-behalf-of flow, so the web api configuration is different than the one in this repo.

Your WebAPI configuration on AzureAD should follow this: https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/tree/master/2.%20Web%20API%20now%20calls%20Microsoft%20Graph#register-the-service-app-todolistservice.

Make sure to set knownClientApplications, and that your SPA app requests an access token for the Web API using the /.default scope.

Another observation is that [AuthorizeForScopes] only works for client apps (web mvc project). It won't work on your API action results.

franva commented 4 years ago

thanks @TiagoBrenck for pointing out the correct example to follow.

I checked my app registration for my API project on Azure, it's almost identical to the one you provided(https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/tree/master/2.%20Web%20API%20now%20calls%20Microsoft%20Graph#register-the-service-app-todolistservice) EXCEPT the Application ID URI. So it seems that all discussions come back to my first question :) Should I use http:// pattern or the api:// patter? I am now totally lost >_<;

Why should I follow the dotnet-native example rather than the aspnetcore example, as in my case I am using Angular app, instead of the WPF client?

Also, in my Web Api and Client App(Angular), I am using the same Client Id as recommended by derisen at here, so for the client app, should I also follow the example you provided for its Azure registration? If I keep using same Client Id, do I still need to set knownClientApplications to include the WebApi App ID?

I do not understand your last sentence : "It work on your API action results" Did you want to say: it doesn't work on your API action results?

Once again, I appreciate your help.

franva commented 4 years ago

I updated my SPA request to use:

"resourceScope": "https://papayee008.onmicrosoft.com/papayee008/.default"

And here is the configuration for the SPA image

and here is the config for Web Api: image (Btw, I also tried "GraphApiUrl": "https://graph.microsoft.com/v1.0" same error.)

But the error persists. image

TiagoBrenck commented 4 years ago

Should I use http:// pattern or the api:// patter? I am now totally lost >_<; Sorry about that. You can choose the one you prefer.

Why should I follow the dotnet-native example rather than the aspnetcore example, as in my case I am using Angular app, instead of the WPF client? I recommended that one just because of the Web API configuration for OBO flow. You can ignore the WPF client part, and just study the web api project.

If I keep using same Client Id, do I still need to set knownClientApplications to include the WebApi App ID? Sorry I didnt know you were using the same client ID. In that case, the example that I provided wont match your scenario

Did you want to say: it doesn't work on your API action results? Ops I miss typed that and edited the answer. That attribute doesn't work on Web API action results.

I am a bit confuse with your scenario since I cant see the whole project with the configurations. I am assuming you have a separate project just for the web api, and from the posts you have on Stackoverflow, it seems that you are missing AddProtectedWebApiCallsProtectedWebApi configuration in your web api startup. That is why I keep pushing you to use the dotnet-native example to configure your Web API. Please compare your current web api configurations with the code there, and make the missing adjustments.

franva commented 4 years ago

hi @TiagoBrenck

I think i finally have made some progress. I found the git repository -> Microsoft.Identity.Web and read through its Readme and understood why you asked me to call AddProtectedWebApiCallsProtectedWebApi. Before diving deeper, I'd like to clarify the name ->AddProtectedWebApiCallsProtectedWebApi

There are 2 ProtectedWebApi in this name. My understanding is, the 1st ProtectedWebApi is my own Web Api and the 2nd ProtectedWebApi is the downstream Microsoft Graph Api? Is my understanding correct?

Now, in the TodoListController, I added the following code:

      using Graph = Microsoft.Graph;
      static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };
        readonly ITokenAcquisition tokenAcquisition;
        readonly WebOptions webOptions;

        private readonly TodoContext _context;

        public TodoListController(TodoContext context, ITokenAcquisition tokenAcquisition, IOptions<WebOptions> webOptionValue)
        {
            this.tokenAcquisition = tokenAcquisition;
            this.webOptions = webOptionValue.Value;
            _context = context;
        }

          [HttpGet("emails")]
        [AuthorizeForScopes(Scopes = new[] { Constants.ScopeUserRead, Constants.ScopeMailRead })]
        public async Task<IActionResult> Profile()
        {
            HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

            var subject = string.Empty;
            try
            {
                // Initialize the GraphServiceClient. 
                Graph::GraphServiceClient graphClient = GetGraphServiceClient(new[] { Constants.ScopeUserRead, Constants.ScopeMailRead });

                var me = await graphClient.Me.Request().GetAsync();
                // Get user photo
                var messages = await graphClient.Me.MailFolders.Inbox.Messages.Request().GetAsync();
                subject = messages.First().Subject;
                return Ok(subject);
            }
            catch (System.Exception ex)
            {
                throw ex;
            }
        }

        private Graph::GraphServiceClient GetGraphServiceClient(string[] scopes)
        {
            return GraphServiceClientFactory.GetAuthenticatedGraphClient(async () =>
            {
                string result = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
                return result;
            }, webOptions.GraphApiUrl);
        }

Startup.cs

        public void ConfigureServices(IServiceCollection services)
        {
            // Setting configuration for protected web api
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddProtectedWebApi(Configuration);

            services.AddProtectedWebApiCallsProtectedWebApi(Configuration).AddInMemoryTokenCaches();

            //services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { Constants.ScopeUserRead, Constants.ScopeMailRead })
            //    .AddInMemoryTokenCaches();

            services.AddOptions();
            services.AddGraphService(Configuration);

            // Creating policies that wraps the authorization requirements
            services.AddAuthorization();

            services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));

            services.AddControllers();

            // Allowing CORS for all domains and methods for the purpose of sample
            services.AddCors(o => o.AddPolicy("default", builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            }));
        }

The added code comes from here

On the Angular app side, I added this code:

TodoService.ts

  getEmails() {
    return this.http.get(this.url + 'emails');
  }

todo-view.component.ts

  getEmails(): void {
    this.service.getEmails().subscribe({
      next: (emails: any) => {
        alert(emails);
      },
      error: (err: any) => {
        console.log("error happened~!");
        console.log(err);
      }
    });
  }

todo-view.component.html, at any proper place, I added this code: <button (click)="getEmails();">Get Emails</button>

Then when I launched the Angular app and signed in, and clicked the Get Emails button, I received a different error:

image

I inspected the exception and found there is no inner exception anymore. The error message is very generic and gives me no clue about what I could do.

With the given code here, I think you should be able to reproduce my issue. Once again, thanks for your consistent update.

franva commented 4 years ago

I added a reference to Microsoft.Identity.Web project so that I can debug inside it.

And I found that the code was trying to fetch value from cache but got null: image

Then it cannot find an account: image

Then throws error here: image

But I have no idea how to get it fixed.

TiagoBrenck commented 4 years ago

Cool, I think we are making progress. Your understanding about the AddProtectedWebApiCallsProtectedWebApi is correct.

Could you get what is null in this last screenshot? Is it the variable tokenUsedToCallTheWebApi?

franva commented 4 years ago

hi @TiagoBrenck the "account" is null, you can see it from the second last screenshot.

Cheers,

Winston

TiagoBrenck commented 4 years ago

@franva would you be able to have a screen sharing session with me, so I can help you troubleshoot this problem?

franva commented 4 years ago

@TiagoBrenck thanks man, Yes, sure. I am based in Melbourne Australia, I can do screen sharing after work 6PM, or anytime in weekends. What is your preferred tool to use? Team? Skype? Zoom? I will add you there.

Thanks again~!

franva commented 4 years ago

hi @TiagoBrenck thanks for your willingness to help.

I have tried, I cannot use my own Microsoft account to log into Team and cannot find you if I logged in with my company's account.

my email is franva008@hotmail.com

Could you please add me on Skype? or any other means you prefer.

Thanks a lot