Earl
Earl

Reputation: 13

Client SPA authentication with Core 3.1 Web API using Microsoft Identity

I think my main question is which Authentication flow should I be using - On-Behalf-Of or Implicit Grant Flow? However my Authentication and Authorization set up in Startup.cs could be wrong and I am way off, so apologies if so.

I have an existing Angular SPA in Azure and a Net Framework API also running in Azure.

The web API has been ported to Net Core 3.1.

In our Azure directory, I have two registered apps. One for the API, and the other for the SPA. I have added the SPA client ID under authorized clients in the WEB API.

It is worth noting that the App services are actually running in a different directory (I think this if fine but just mentioning)

Here is the relevant Authentication/Authorization setup in Azure AD.

    private void ConfigureAzureAD(IServiceCollection services, IMvcBuilder mvcBuilder)
        {
            services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
                .AddAzureAD(options => Configuration.Bind("AzureAd", options));

            services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
            {
                options.Authority = options.Authority + "/v2.0/";
                options.TokenValidationParameters.ValidateIssuer = true;
            });

            mvcBuilder.AddMvcOptions(options =>
            {
                var policy = new AuthorizationPolicyBuilder().
                    RequireAuthenticatedUser().
                    Build();
                options.Filters.Add(new AuthorizeFilter(policy));
            }).
            SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseResponseCaching();
            }

            app.UseStaticFiles();
            app.UseCors(CORS_POLICY);


            if (UseAzureAD)
            {
                app.UseHttpsRedirection();
            }

            app.UseRouting();
            app.UseCookiePolicy();
            app.UseAuthorization();
            app.UseAuthentication();

            app.UseMvc();
        }

I can successfully use the API directly and it authenticates.

The SPA is using Angular ADAL. Which if I read correctly will not work for Microsoft Identity V2 endpoints. - we need to upgrade to MSAL still, although I am still having trouble understanding the ultimate authentication flow (which I think is my main question).

Currently, if I hit the SPA, it will authenticate just using the current ADAL Config, but when it hits the API, and gets redirected to https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/authorize? with and ID Token.

It hangs on the redirect and their is a CORS error because the origin is null. I know the is expected as the SPA is not configured correctly.

The API does make calls to MS Graph API to get groups for the user (but uses client ID and client secret to get an access token to call MS Graph), so I don't think this means I need to use the On-Behalf-Of-Flow (I did see this post Authenticating against Microsoft Graph with SPA and then using token in Web API)?

I have looked at this example https://github.com/Azure-Samples/ms-identity-javascript-angular-spa-aspnetcore-webapi for implicit grant flow.

I did notice in that example the authentication scheme is JwtBearerDefualts which makes sense as as ID Tokens are JWTs, so that makes me question my API startup config.

Here is this bit that calls MS Graph in the API which is probably needed to best answer which of the 2 flows I should use (again it gets a access token with client secret).

private async Task<IReadOnlyCollection<string>> LoadReportGroupsCurrentUserCanRead()
        {
            var objectID = claimsPrincipal
                .Claims
                .FirstOrDefault(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier")
                ?.Value;

            var graphServiceClient = await GetGraphServiceClient();

            var groups = await graphServiceClient
                .Groups
                .Request()
                .GetAsync();

            var memberGroups = await graphServiceClient
                .Users[objectID]
                .GetMemberGroups(true)
                .Request()
                .PostAsync();

            return memberGroups.Select(mg => groups.SingleOrDefault(g => g.Id == mg))
                .Where(g => g?.DisplayName?.EndsWith(" Report Group", StringComparison.InvariantCultureIgnoreCase) == true)
                .Select(g => g.Description)
                .Where(name => !string.IsNullOrWhiteSpace(name))
                .ToArray();
        }

        private async Task<GraphServiceClient> GetGraphServiceClient()
        {

            string authority = new Uri(microsoftLogin, tenantID).AbsoluteUri;
            var authenticationContext = new AuthenticationContext(authority);
            var clientCredential = new ClientCredential(clientID, clientSecret);

            var authenticationResult = await authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCredential);

            var authProvider = new DelegateAuthenticationProvider(requestMessage =>
            {
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
                return Task.CompletedTask;
            });
            return new GraphServiceClient(authProvider);
        }

Lastly, I think our SPA is using a version of Angular that does not support Angular MSAL. Can I just use Vanilla MSAL JS (Don't think we have time right now to upgrade Angular)?

Upvotes: 1

Views: 940

Answers (1)

AlfredoRevilla-MSFT
AlfredoRevilla-MSFT

Reputation: 3485

You should be using implicit flow and msal-angular. It supports Angular from 4 to 9. Also I would not recommend using vanilla libraries in Angular unless there is no other choice.

ADAL is deprecated and does not support the converged application model (work+personal accounts) so you need to move to MSAL.

Follow Create web APIs with ASP.NET Core and Migrate from ASP.NET Core 2.2 to 3.0 to remove code like this:

mvcBuilder.AddMvcOptions(options =>
            {
                var policy = new AuthorizationPolicyBuilder().
                    RequireAuthenticatedUser().
                    Build();
                options.Filters.Add(new AuthorizeFilter(policy));
            })

And instead base your controller from Controller and decorate them with ApiController attribute.

And also this since it's .NET 3.1:

.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

Your MS Graph authentication is being authenticated with client credentials which is a commmon approach for the current app architecture. Provided you're not adding way too many highly privileged permissions then it should be fine.

Upvotes: 1

Related Questions