Quantcast
Channel: ASP.NET Core
Viewing all articles
Browse latest Browse all 9386

Having issue with my Microsoft.Identity.Client.ConfidentialClientApplication object having empty "Users" property

$
0
0

I am having an issue with my Microsoft.Identity.Client.ConfidentialClientApplication object having an empty "Users" property (belongs to the "Microsoft.Identity.Client.Client​Application​Base" class). Another issue that I have is that my event function "OnAuthorizationCodeReceived" in the "Microsoft.AspNetCore.Authentication.OpenIdConnect.Open​Id​Connect​Events" class does not ever seem to get called, as I was unable to see any test output that I logged in that function. Isn't it true that you need to get an authorization code, i.e., call the "Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenByAuthorizationCodeAsync" function to store the code in your cache using MSAL before you try to retrieve the same authorization code in your cache using MSAL, i.e., call "Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenSilentAsync"?

Here is my "Configure" function, where I am initializing the properties of the Microsoft.AspNetCore.Builder.OpenIdConnectOptions.Events object with the event functions "Microsoft.AspNetCore.Builder.OpenIdConnectOptions.Events.OnRemoteFailure" (belonging to the Microsoft.AspNetCore.Authentication.Remote​Authentication​Events class) and "OnAuthorizationCodeReceived" (belonging to the Microsoft.AspNetCore.Authentication.OpenIdConnect.Open​Id​Connect​Events class):

        public void Configure(OpenIdConnectOptions options)
        {
            _logger.LogInformation("in public void Configure(OpenIdConnectOptions options)");

            options.ClientId = _azuread.ClientId;
            options.Authority = string.Format(CultureInfo.InvariantCulture, _azuread.AadInstance, _azuread.Tenant, "/v2.0");
            options.UseTokenLifetime = true;
            options.TokenValidationParameters = new TokenValidationParameters() {
                ValidateIssuer = false,
                NameClaimType = "name"
            };

            options.Events = new OpenIdConnectEvents()
            {
                //  OnRedirectToIdentityProvider = OnRedirectToIdentityProvider,
                OnRemoteFailure = OnRemoteFailure,
                OnAuthorizationCodeReceived = OnAuthorizationCodeReceived
            };
        }

The "Configure" function above should have been called in the "ConfigureDevelopmentServices" in my "Startup" class, where I added a singleton service of the type "IConfigureOptions<OpenIdConnectOptions>", and this service holds the "OpenIdConnectOptionsSetup" class (user-defined) that inherits from "Microsoft.Extensions.Options.IConfigureOptions<Microsoft.AspNetCore.Builder.OpenIdConnectOptions>". Here is my "ConfigureDevelopmentServices" function:

        public void ConfigureDevelopmentServices(IServiceCollection services)
        {
            services.Configure<MvcOptions>(options =>
            {
                options.Filters.Add(new RequireHttpsAttribute());
            });

            // Add framework services.
            services.AddDbContext<CenterContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            // Add framework services.
            services.AddApplicationInsightsTelemetry(Configuration);

            // Adds services required for using options.
            services.AddOptions();

            // Register the IConfiguration instance which MyOptions binds against.
            services.Configure<ReCaptcha>(Configuration.GetSection("ReCaptcha"));
            services.Configure<AzureAd>(Configuration.GetSection("AzureAd:Development"));
            services.Configure<OAuth2>(Configuration.GetSection("OAuth2:Development"));

            // Adds a default in-memory implementation of IDistributedCache.
            services.AddDistributedMemoryCache();
            services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.FromHours(1);
                options.CookieHttpOnly = true;
            });

            services.AddMvc();

            // Add Authentication services.
            services.AddAuthentication(sharedOptions =>
            sharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);

            services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, OpenIdConnectOptionsSetup>();
        }

I know that the "Configure" function in my "OpenIdConnectOptionsSetup" class (user-defined) is getting called (having seen output logs in that function), but I am not sure if either my "OnRemoteFailure" or "OnAuthorizationCodeReceived" are getting called at all, as they are event functions. Again, isn't "AcquireTokenByAuthorizationCodeAsync" in "OnAuthorizationCodeReceived" supposed to be called before "AcquireTokenSilentAsync"? Here are my "OnRemoteFailure" and "OnAuthorizationCodeReceived" functions:

        private Task OnRemoteFailure(FailureContext context)
        {
            /*
            context.HandleResponse();
            string redirect = "/Home/Error?message=" + context.Exception.Message;
            if (context.ProtocolMessage != null && !string.IsNullOrEmpty(context.ProtocolMessage.ErrorDescription))
            {
                redirect += "&debug=" + context.ProtocolMessage.ErrorDescription;
            }
            context.Response.Redirect(redirect);
            return Task.FromResult(0);
            */
            context.HandleResponse();
            context.Response.Redirect("/Home/Error?message=" + context.Failure.Message);
            return Task.FromResult(0);
        }

        public async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
        {
            /*

            string signedInUserID = context.Ticket.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
            //  TokenCache userTokenCache = new MSALSessionCache(signedInUserID, context.HttpContext).GetMsalCacheInstance();
            TokenCache userTokenCache = new SessionTokenCache(signedInUserID, context.HttpContext).GetMsalCacheInstance();
            ConfidentialClientApplication cca = new ConfidentialClientApplication(_azuread.ClientId,
                string.Format(CultureInfo.InvariantCulture, _azuread.AadInstance, _azuread.Tenant, "/v2.0"),
                _oauth2.RedirectUri,
                new ClientCredential(_azuread.ClientSecret),
                userTokenCache,
                null);
            try
            {
                AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, _oauth2.AppScopes.Split(' '));
                context.HandleCodeRedemption(result.AccessToken, result.IdToken);
            }
            catch (Exception ex)
            {
                //TODO: Handle
                throw;
            }
            */

            // Use MSAL to swap the code for an access token
            // Extract the code from the response notification
            var code = context.ProtocolMessage.Code;

            string userId = context.Ticket.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
            //  Microsoft.Identity.Client.TokenCache tokenCache = new SessionTokenCache(userId, context.HttpContext).GetMsalCacheInstance();
            //  NaiveSessionCache tokenCache = new NaiveSessionCache(userId, context.HttpContext.Session);
            Microsoft.Identity.Client.TokenCache tokenCache = new SessionTokenCache(userId, context.HttpContext).GetMsalCacheInstance();

            // Acquire a Token for the Graph API and cache it using ADAL.  In the TodoListController, we'll use the cache to acquire a token to the Todo List API
            Microsoft.Identity.Client.ClientCredential clientCred = new Microsoft.Identity.Client.ClientCredential(_azuread.ClientSecret);
            //  Microsoft.Identity.Client.ClientCredential clientCred = new Microsoft.Identity.Client.ClientCredential(_azuread.ClientSecret);
            //  AuthenticationContext authContext = new AuthenticationContext(string.Format(CultureInfo.InvariantCulture, _azuread.AadInstance, _azuread.Tenant, "/v2.0"), tokenCache);
            /*
            Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
                context.ProtocolMessage.Code, new Uri(context.Properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey]), clientCred, _azuread.GraphResourceId);
            */
            ConfidentialClientApplication cca = new ConfidentialClientApplication(_azuread.ClientId,
                string.Format(CultureInfo.InvariantCulture, _azuread.AadInstance, _azuread.Tenant, "/v2.0"),
                _oauth2.RedirectUri,
                clientCred,
                tokenCache,
                null);

            try
            {
                Microsoft.Identity.Client.AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, _oauth2.AppScopes.Split(' '));
                context.HandleCodeRedemption(result.AccessToken, result.IdToken);
            }
            catch (Exception ex)
            {
                //TODO: Handle
                throw;
            };
        }

I am not sure if either of these functions gets called, since in the following function "GetAccessToken" (user-defined), I am trying to iterate through the ConfidentialClientApplication object's "Users", but "Users" is empty (I was expecting "Users" to have a count of one):

        public async Task<string> GetAccessToken()
        {
            string accessToken = null;

            // Load the app config from web.config
            string appId = _oauth2.AppId;
            string appPassword = _oauth2.AppPassword;
            string redirectUri = _oauth2.RedirectUri;
            string[] appScopes = _oauth2.AppScopes
                .Replace(' ', ',').Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            _logger.LogInformation("appId = " + appId);
            _logger.LogInformation("appPassword = " + appPassword);
            _logger.LogInformation("redirectUri = " + redirectUri);
            _logger.LogInformation("appScopes = " + appScopes.ToString());

            ClaimsPrincipal principal = User as ClaimsPrincipal;
            ClaimsIdentity principalIdentity = principal.Identity as ClaimsIdentity;

            //  Get the current user's ID
            //  string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
            string userId = principalIdentity.FindFirst(ClaimTypes.NameIdentifier).Value;

            if (!string.IsNullOrEmpty(userId))
            {
                // Get the user's token cache
                Microsoft.Identity.Client.TokenCache tokenCache = new SessionTokenCache(userId, HttpContext).GetMsalCacheInstance();
                //  Microsoft.Identity.Client.TokenCache tokenCache = new NaiveSessionCache(userId, HttpContext.Session);

                ConfidentialClientApplication cca = new ConfidentialClientApplication(
                    appId, redirectUri, new Microsoft.Identity.Client.ClientCredential(appPassword), tokenCache, null);

                _logger.LogInformation("inside if (!string.IsNullOrEmpty(userId))");

                var ccaUsersGetEnumerator = cca.Users.GetEnumerator();

                if (ccaUsersGetEnumerator.MoveNext())
                {

                    // Call AcquireTokenSilentAsync, which will return the cached
                    // access token if it has not expired. If it has expired, it will
                    // handle using the refresh token to get a new one.
                    _logger.LogInformation("inside if (ccaUsersGetEnumerator.MoveNext())");

                    Microsoft.Identity.Client.AuthenticationResult result = await cca.AcquireTokenSilentAsync(appScopes, ccaUsersGetEnumerator.Current);
                    accessToken = result.AccessToken;
                }
            }

            /*
            // Acquire a Token for the Graph API and cache it using ADAL.  In the TodoListController, we'll use the cache to acquire a token to the Todo List API
            ClientCredential clientCred = new ClientCredential(appId, appPassword);
            AuthenticationContext authContext = new AuthenticationContext(string.Format(CultureInfo.InvariantCulture, _azuread.AadInstance, _azuread.Tenant, "/v2.0"), new NaiveSessionCache(userId, HttpContext.Session));
            AuthenticationResult authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
                HttpContext.ProtocolMessage.Code, new Uri(context.Properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey]), clientCred, _azureadgraphresourceid);

            // Notify the OIDC middleware that we already took care of code redemption.
            context.HandleCodeRedemption(authResult.AccessToken, authResult.IdToken);
            */

            return accessToken;
        }

Check out the following screenshots that show the issue that I am having:

Link to screenshot 1

Link to screenshot 2

"GetAccessToken" is a function that I defined in my controller when a user clicks a link to access his or her Outlook mail inbox. By the way, my temporary goal is for a user to retrieve the emails in his or her Outlook mail inbox in my web app, so that I know that a user can do anything that is supported by the Microsoft Graph API. Here is the function where I am calling "GetAccessToken" (user-defined):

        public async Task<ActionResult> Inbox()
        {
            string token = await GetAccessToken();
            if (string.IsNullOrEmpty(token))
            {
                // If there's no token in the session, redirect to Home
                return Redirect("/");
            }

            string userEmail = await GetUserEmail();

            GraphServiceClient client = new GraphServiceClient(
                new DelegateAuthenticationProvider(
                    (requestMessage) =>
                    {
                        requestMessage.Headers.Authorization =
                            new AuthenticationHeaderValue("Bearer", token);

                        requestMessage.Headers.Add("X-AnchorMailbox", userEmail);

                        return Task.FromResult(0);
                    }));

            try
            {
                var mailResults = await client.Me.MailFolders.Inbox.Messages.Request()
                                    .OrderBy("receivedDateTime DESC")
                                    .Select("subject,receivedDateTime,from")
                                    .Top(10)
                                    .GetAsync();

                return View(mailResults.CurrentPage);
            }
            catch (ServiceException ex)
            {
                return RedirectToAction("Error", "Home", new { message = "ERROR retrieving messages", debug = ex.Message });
            }
        }

Thank you for your assistance!


Viewing all articles
Browse latest Browse all 9386

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>