robinvanderknaap / authorization-server-openiddict

Authorization Server implemented with OpenIddict.
MIT License
143 stars 51 forks source link

Having cookie problem using Identity Login Razor Page and AuthorizeController #3

Open stlsw opened 2 years ago

stlsw commented 2 years ago

Thanks for your amazing project and guide on Dev.to. It is running perfectly fine, but I noticed your Login is under controller instead of PageModel, I'm trying to figure out the cookie issue after user logging in successfully in PageModel and it will go into login loops and var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); this will return nothing.

After I disabled PFCK, the authentication shows "Unprotect ticket failed" and System.Security.Cryptography.CryptographicException: The payload was invalid.

Does it mean Identity Pages can not work with Openiddict to act as an Authorization Server ?

The below code is Login Page.

public class LoginModel : PageModel
    {
        private readonly CustomUserManager _userManager;
        private readonly CustomSignInManager _signInManager;
        private readonly AccountManager _accountManager;
        private readonly ILogger<LoginModel> _logger;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="signInManager"></param>
        /// <param name="logger"></param>
        /// <param name="userManager"></param>
        /// <param name="accountManager"></param>
        public LoginModel(CustomSignInManager signInManager,
            ILogger<LoginModel> logger,
            CustomUserManager userManager,
            AccountManager accountManager)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _logger = logger;
        }

        /// <summary>
        /// 
        /// </summary>
        [BindProperty]
        public InputModel Input { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public IList<AuthenticationScheme> ExternalLogins { get; set; }

        public string ReturnUrl { get; set; }
        [BindProperty]
        public string LoginType { get; set; } = "email";

        [TempData]
        public string ErrorMessage { get; set; }

        public class InputModel
        {
            [RequiredIf(nameof(EmailOrPhoneNumberRadio), "email",ErrorMessage = "Email address is required.")]
            public string Email { get; set; }
            [RequiredIf(nameof(EmailOrPhoneNumberRadio), "phone", ErrorMessage = "Phone number is required.")]
            public string PhoneNumber { get; set; }

            public string EmailOrPhoneNumberRadio { get; set; }

            [DataType(DataType.Password)]
            public string Password { get; set; }

            public string Otp { get; set; }

            [Display(Name = "Remember me?")]
            public bool RememberMe { get; set; }
        }

        public async Task OnGetAsync(string returnUrl = null)
        {
            if (!string.IsNullOrEmpty(ErrorMessage))
            {
                ModelState.AddModelError(string.Empty, ErrorMessage);
            }

            returnUrl = returnUrl ?? Url.Content("~/");

            // Clear the existing external cookie to ensure a clean login process
            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

            ReturnUrl = returnUrl;
        }

        public async Task<IActionResult> OnPostAsync(string returnUrl = null, string loginType = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            LoginType = loginType;
            // Remove unselected username input
            if (Input.EmailOrPhoneNumberRadio == "email") Input.PhoneNumber = string.Empty;
            else Input.Email = string.Empty;
            if (ModelState.IsValid)
            {
                //if (Input.EmailOrPhoneNumberRadio == "email")
                //{
                //    _accountManager.GetTokenUsingEmailPasswordGrantAsync()
                //}
                //else
                //{

                //}
                // This doesn't count login failures towards account lockout
                // To enable password failures to trigger account lockout, set lockoutOnFailure: true
                var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
                if (result.Succeeded)
                {
                    _logger.LogInformation("User logged in.");
                    var user = await _userManager.FindByEmailAsync(Input.Email);
                    // Subject
                    var claims = new List<Claim>
                    {
                        new Claim(OpenIddictConstants.Claims.Subject, user.Id.ToString())
                    };
                    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
                    await HttpContext.SignInAsync(new ClaimsPrincipal(claimsIdentity));
                    return Redirect(returnUrl);
                }
                if (result.RequiresTwoFactor)
                {
                    return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
                }
                if (result.IsLockedOut)
                {
                    ModelState.AddModelError(string.Empty, "User account locked out.");
                    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
                    return Page();
                }
                else
                {
                    ModelState.AddModelError(string.Empty, "Username or password is incorrect.");
                    LoginType = Input.EmailOrPhoneNumberRadio;
                    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
                    return Page();
                }
            }

            // If we got this far, something failed, redisplay form
            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
            return Page();
        }
    }

public class AuthorizationController : Controller
    {
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        [HttpGet("~/connect/authorize")]
        [HttpPost("~/connect/authorize")]
        public async Task<IActionResult> Authorize()
        {
            var request = HttpContext.GetOpenIddictServerRequest() ??
                          throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");

            // Retrieve the user principal stored in the authentication cookie.
            var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            // If the user principal can't be extracted, redirect the user to the login page.
            if (!result.Succeeded)
            {
                return Challenge(
                    authenticationSchemes: CookieAuthenticationDefaults.AuthenticationScheme,
                    properties: new AuthenticationProperties
                    {
                        RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
                            Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
                    });
            }

            // Create a new claims principal
            var claims = new List<Claim>
            {
                // 'subject' claim which is required
                new Claim(OpenIddictConstants.Claims.Subject, result.Principal.Identity.Name),
                new Claim("some claim", "some value").SetDestinations(OpenIddictConstants.Destinations.AccessToken),
                new Claim(OpenIddictConstants.Claims.Email, "some@email").SetDestinations(OpenIddictConstants.Destinations.IdentityToken)
            };

            var claimsIdentity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

            var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

            // Set requested scopes (this is not done automatically)
            claimsPrincipal.SetScopes(request.GetScopes());

            // Signing in with the OpenIddict authentiction scheme trigger OpenIddict to issue a code (which can be exchanged for an access token)
            return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }
    }
stlsw commented 2 years ago

I finally figure it out and hope it will help others who stuck here. just have to change var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); to var result = await HttpContext.AuthenticateAsync("Identity.Application"); it probably due to because my app uses aspnet identity core. cheers