title: Custom login UI for IdentityServer 4
body: <p>I am doing a project that is similar to IdentityServer4.Quickstart.UI, but separate the Login UI and IdentityServer. More specifically, the IdentityServer itself will be a pure WebApi and the Login UI can be any SPA built with Angualr or React or .... Currently, I am facing an issue <code>Identity.Application was not authenticated. Failure message: Unprotect ticket failed</code>.</p>
  
  <p>The more detailed process is as: after clicking the login button in the SPA page, the following code</p>
  
  <p><code>var result = await this._signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);</code></p>
  
  <p>will be run in the WebApi(IdentityServer) and the <code>result</code> is succeeded (user is logged in, which can be seen in UserSession in IdentityServer code). </p>
  
  <p>Following the procedure in IdentityServer4.Quickstart.UI, the UI will be redirected to <code>/connect/authorize/callback</code> endpoint. However when I redirect the UI to <code>/connect/authorize/callback</code>, <strong>the user is no longer logged in</strong>. The error is <code>Identity.Application was not authenticated. Failure message: Unprotect ticket failed</code>. Please help to see what could be wrong. Some people said it is because the authentication cookie but I have no idea how to do it.</p>
  
  <p>The detailed log is attached as follow</p>
  
  <pre><code>info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
        User profile is available. Using 'C:\Users\Admin\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
  info: IdentityServer4.Startup[0]
        You are using the in-memory version of the persisted grant store. This will store consent decisions, authorization codes, refresh and reference tokens in memory only. If you are using any of those features in production, you want to switch to a different store implementation.
  dbug: IdentityServer4.Startup[0]
        Custom IssuerUri set to http://localhost:5000
  dbug: IdentityServer4.Startup[0]
        Using Identity.Application as default ASP.NET Core scheme for authentication
  dbug: IdentityServer4.Startup[0]
        Using Identity.External as default ASP.NET Core scheme for sign-in
  dbug: IdentityServer4.Startup[0]
        Using Identity.External as default ASP.NET Core scheme for sign-out
  dbug: IdentityServer4.Startup[0]
        Using Identity.Application as default ASP.NET Core scheme for challenge
  dbug: IdentityServer4.Startup[0]
        Using Identity.Application as default ASP.NET Core scheme for forbid
  Hosting environment: Development
  Content root path: C:\Users\Admin\Downloads\Programming\IDS\Server
  Now listening on: http://localhost:5000
  Application started. Press Ctrl+C to shut down.
  info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
        Request starting HTTP/1.1 OPTIONS http://localhost:5000/Account/Login?returnUrl=http://localhost:5000/connect/authorize/callback?client_id=ce&amp;redirect_uri=http%3A%2F%2Flocalhost%3A5050&amp;response_type=id_token%20token&amp;scope=openid%20profile%20CE&amp;nonce=N0.90507026110735561540718014359&amp;state=15407087903260.1454827167180146
  info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
        Policy execution successful.
  info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
        Request finished in 20.8366ms 204
  info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
        Request starting HTTP/1.1 POST http://localhost:5000/Account/Login?returnUrl=http://localhost:5000/connect/authorize/callback?client_id=ce&amp;redirect_uri=http%3A%2F%2Flocalhost%3A5050&amp;response_type=id_token%20token&amp;scope=openid%20profile%20CE&amp;nonce=N0.90507026110735561540718014359&amp;state=15407087903260.1454827167180146 application/json 61
  info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
        Policy execution successful.
  dbug: IdentityServer4.Hosting.CorsPolicyProvider[0]
        CORS request made for path: /Account/Login from origin: http://localhost:5001
  dbug: IdentityServer4.Hosting.CorsPolicyProvider[0]
        CorsPolicyService allowed origin: http://localhost:5001
  info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
        Policy execution successful.
  dbug: IdentityServer4.Startup[0]
        Login Url: http://localhost:5001/login
  dbug: IdentityServer4.Startup[0]
        Login Return Url Parameter: ReturnUrl
  dbug: IdentityServer4.Startup[0]
        Logout Url: /Account/Logout
  dbug: IdentityServer4.Startup[0]
        ConsentUrl Url: /consent
  dbug: IdentityServer4.Startup[0]
        Consent Return Url Parameter: returnUrl
  dbug: IdentityServer4.Startup[0]
        Error Url: /home/error
  dbug: IdentityServer4.Startup[0]
        Error Id Parameter: errorId
  info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
        Route matched with {action = "Login", controller = "Account"}. Executing action Server.Controllers.AccountController.Login (Server)
  info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
        Executing action method Server.Controllers.AccountController.Login (Server) with arguments (Server.Dto.Login) - Validation state: Valid
  info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
        Entity Framework Core 2.1.4-rtm-31024 initialized 'DbContext' using provider 'Pomelo.EntityFrameworkCore.MySql' with options: None
  info: Microsoft.EntityFrameworkCore.Database.Command[20101]
        Executed DbCommand (88ms) [Parameters=[@__normalizedUserName_0='?' (Size = 256)], CommandType='Text', CommandTimeout='30']
        SELECT `u`.`Id`, `u`.`AccessFailedCount`, `u`.`ConcurrencyStamp`, `u`.`Email`, `u`.`EmailConfirmed`, `u`.`LockoutEnabled`, `u`.`LockoutEnd`, `u`.`NormalizedEmail`, `u`.`NormalizedUserName`, `u`.`PasswordHash`, `u`.`PhoneNumber`, `u`.`PhoneNumberConfirmed`, `u`.`SecurityStamp`, `u`.`TwoFactorEnabled`, `u`.`UserName`
        FROM `AspNetUsers` AS `u`
        WHERE `u`.`NormalizedUserName` = @__normalizedUserName_0
        LIMIT 1
  info: Microsoft.EntityFrameworkCore.Database.Command[20101]
        Executed DbCommand (6ms) [Parameters=[@__user_Id_0='?' (Size = 255)], CommandType='Text', CommandTimeout='30']
        SELECT `uc`.`Id`, `uc`.`ClaimType`, `uc`.`ClaimValue`, `uc`.`UserId`
        FROM `AspNetUserClaims` AS `uc`
        WHERE `uc`.`UserId` = @__user_Id_0
  info: Microsoft.EntityFrameworkCore.Database.Command[20101]
        Executed DbCommand (30ms) [Parameters=[@__userId_0='?' (Size = 255)], CommandType='Text', CommandTimeout='30']
        SELECT `role`.`Name`
        FROM `AspNetUserRoles` AS `userRole`
        INNER JOIN `AspNetRoles` AS `role` ON `userRole`.`RoleId` = `role`.`Id`
        WHERE `userRole`.`UserId` = @__userId_0
  dbug: IdentityServer4.Hosting.IdentityServerAuthenticationService[0]
        Augmenting SignInContext
  dbug: IdentityServer4.Hosting.IdentityServerAuthenticationService[0]
        Adding idp claim with value: local
  dbug: IdentityServer4.Hosting.IdentityServerAuthenticationService[0]
        Adding amr claim with value: pwd
  dbug: IdentityServer4.Hosting.IdentityServerAuthenticationService[0]
        Adding auth_time claim with value: 1540720363
  info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[10]
        AuthenticationScheme: Identity.Application signed in.
  info: Server.Controllers.AccountController[0]
        User logged in.
  info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
        Executed action method Server.Controllers.AccountController.Login (Server), returned result Microsoft.AspNetCore.Mvc.OkResult in 25685.0508ms.
  info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
        Executing HttpStatusCodeResult, setting HTTP status code 200
  info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
        Executed action Server.Controllers.AccountController.Login (Server) in 26016.4477ms
  info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
        Request finished in 26239.7052ms 200
  info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
        Request starting HTTP/1.1 GET http://localhost:5000/connect/authorize/callback?client_id=ce&amp;redirect_uri=http%3A%2F%2Flocalhost%3A5050&amp;response_type=id_token%20token&amp;scope=openid%20profile%20CE&amp;nonce=N0.90507026110735561540718014359&amp;state=15407087903260.1454827167180146
  info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[7]
        Identity.Application was not authenticated. Failure message: Unprotect ticket failed
  info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[7]
        Identity.Application was not authenticated. Failure message: Unprotect ticket failed
  dbug: IdentityServer4.Hosting.EndpointRouter[0]
        Request path /connect/authorize/callback matched to endpoint type Authorize
  dbug: IdentityServer4.Hosting.EndpointRouter[0]
        Endpoint enabled: Authorize, successfully created handler: IdentityServer4.Endpoints.AuthorizeCallbackEndpoint
  info: IdentityServer4.Hosting.IdentityServerMiddleware[0]
        Invoking IdentityServer endpoint: IdentityServer4.Endpoints.AuthorizeCallbackEndpoint for /connect/authorize/callback
  dbug: IdentityServer4.Endpoints.AuthorizeCallbackEndpoint[0]
        Start authorize callback request
  info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[7]
        Identity.Application was not authenticated. Failure message: Unprotect ticket failed
  dbug: IdentityServer4.Endpoints.AuthorizeCallbackEndpoint[0]
        No user present in authorize request
  dbug: IdentityServer4.Validation.AuthorizeRequestValidator[0]
        Start authorize request protocol validation
  dbug: IdentityServer4.Stores.ValidatingClientStore[0]
        client configuration validation for client ce succeeded.
  dbug: IdentityServer4.Validation.AuthorizeRequestValidator[0]
        Calling into custom validator: IdentityServer4.Validation.DefaultCustomAuthorizeRequestValidator
  info: IdentityServer4.Endpoints.AuthorizeCallbackEndpoint[0]
        ValidatedAuthorizeRequest
        {
          "ClientId": "ce",
          "ClientName": "ce",
          "RedirectUri": "http://localhost:5050",
          "AllowedRedirectUris": [
            "http://localhost:5050"
          ],
          "SubjectId": "anonymous",
          "ResponseType": "id_token token",
          "ResponseMode": "fragment",
          "GrantType": "implicit",
          "RequestedScopes": "openid profile CE",
          "State": "15407087903260.1454827167180146",
          "Nonce": "N0.90507026110735561540718014359",
          "Raw": {
            "client_id": "ce",
            "redirect_uri": "http://localhost:5050",
            "response_type": "id_token token",
            "scope": "openid profile CE",
            "nonce": "N0.90507026110735561540718014359",
            "state": "15407087903260.1454827167180146"
          }
        }
  info: IdentityServer4.ResponseHandling.AuthorizeInteractionResponseGenerator[0]
        Showing login: User is not authenticated
  info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
        Request finished in 970.0804ms 302
  </code></pre>
  
  <p>Here is the key part of StartUp.cs</p>
  
  <pre><code>services.AddDbContext&lt;DbContext&gt;(options =&gt;
              options.UseMySql("server=10.1.1.228;database=IdentityService;user=admin;password=admin"));
  
          services.AddCors(options =&gt;
          {
              options.AddPolicy("default", policy =&gt;
              {
                  policy.AllowAnyOrigin()
                      .AllowAnyHeader()
                      .AllowAnyMethod();
              });
          });
  
          services.AddIdentity&lt;SsoUser, IdentityRole&gt;(options =&gt;
          {
              options.Tokens.ChangePhoneNumberTokenProvider = "Phone";
  
              options.Password.RequiredLength = 8;
              options.Password.RequiredUniqueChars = 0;
              options.Password.RequireDigit = false;
              options.Password.RequireLowercase = false;
              options.Password.RequireUppercase = false;
              options.Password.RequireNonAlphanumeric = false;
          })
              .AddEntityFrameworkStores&lt;DbContext&gt;()
              .AddDefaultTokenProviders();
  
          services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
          services.AddIdentityServer(x =&gt;
              {
                  x.IssuerUri = "http://localhost:5000";
                  x.UserInteraction.LoginUrl = "http://localhost:5001/login";
                  x.Cors.CorsPaths.Add(new PathString("/Account/Login"));
              }
          ).AddDeveloperSigningCredential()
              .AddAspNetIdentity&lt;SsoUser&gt;()
              .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
              .AddInMemoryApiResources(InMemoryConfiguration.ApiResources())
              .AddInMemoryClients(InMemoryConfiguration.Clients())
              .AddTestUsers(InMemoryConfiguration.Users().ToList())
              .AddCorsPolicyService&lt;CorsPolicyService&gt;();
  
          services.AddAuthentication().AddCookie();
  </code></pre>