public async Task <IActionResult> Callback(string returnUrl) { if (string.IsNullOrEmpty(returnUrl)) { returnUrl = "~/"; } ; // Validate returnUrl - either it is a valid OIDC URL or back to a local page. if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false) { // User might have clicked on a malicious link - should be logged. throw new Exception("Invalid return URL."); } // Read external identity from the temporary cookie. // At this point registered claims transformation will run (the ClaimsTransformer class). // This is triggered when HttpContext.AuthenticateAsync method is called (which is done inside GetExternalLoginInfoAsync method of SignInManager). var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync(); if (externalLoginInfo == null) { throw new Exception($"Cannot read external login information from {externalLoginInfo.LoginProvider}."); } var user = await _userManager.FindByLoginAsync(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey); // If user with the specified login is null, this means that we have to do with a brand new user. if (user == null) { // Retrieve claims of the external user. var claims = externalLoginInfo.Principal.Claims.ToList(); // We can choose to auto-provision the user or initiate a custom workflow for user registration. user = await AutoProvisionExternalUserAsync(Guid.NewGuid().ToString(), claims); // Save user external login. await _userManager.AddLoginAsync(user, externalLoginInfo); } await _events.RaiseAsync(new UserLoginSuccessEvent(externalLoginInfo.LoginProvider, externalLoginInfo.Principal.GetSubjectId(), user.Id, user.UserName)); // Save user tokes retrieved from external provider. await _signInManager.UpdateExternalAuthenticationTokensAsync(externalLoginInfo); await _signInManager.ExternalLoginSignInAsync(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey, isPersistent : true); // Check if external login is in the context of an OIDC request. var context = await _interaction.GetAuthorizationContextAsync(returnUrl); if (context != null && await _clientStore.IsPkceClientAsync(context.ClientId)) { // If the client is PKCE then we assume it's native, so this change in how to return the response is for better UX for the end user. return(View("Redirect", new RedirectViewModel { RedirectUrl = returnUrl })); } return(Redirect(returnUrl)); }