0
votes

I am using Identity Server 3 with Entity Framework. My ASP.NET MVC app logs in to the SSO/IdentityServer app using below configuration and then that access token is saved in a cookie which is used by javascript to call our API.

Problem is when I login to my ASP.NET MVC app then I go to database and delete that token from the database table, then my API says invalid bearer token as expected, but when I the refresh page in the ASP.NET MVC app, it still shows as logged in and I think it's because of cookie configuration.

How can I ask MVC app to always validate token from server?

AuthConfig.cs of ASP.NET MVC application:

public static class AuthConfig
{
        public static void RegisterAuth(IAppBuilder app)
        {
            ServicePointManager.ServerCertificateValidationCallback =
                (sender, certificate, chain, sslPolicyErrors) => true;

            JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "Cookies",
                SlidingExpiration = true,
                ExpireTimeSpan = SellutionConstants.Globals.AccessTokenExpirationTimeSpan
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                ClientId = "sellutionapp",
                Authority = SsoConfigHelper.SellutionSts,
                ResponseType = "code id_token",
                Scope = "openid profile roles all_claims " + SsoConfigHelper.SellutionApiScope,
                UseTokenLifetime = false,

                TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name",
                    RoleClaimType = "role",
                },

                SignInAsAuthenticationType = "Cookies",

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        // use the code to get the access and refresh token
                        var tokenClient = new TokenClient(
                            SsoConfigHelper.SellutionStsTokenEndpoint,
                            "sellutionapp",
                            "secret");

                        if (String.IsNullOrEmpty(n.RedirectUri))
                        {
                            n.RedirectUri = n.Request.Scheme + "://" + n.Request.Host + n.Request.PathBase;
                        }

                        var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
                        if (tokenResponse.IsError)
                        {
                            throw new Exception(tokenResponse.Error);
                        }


                        // use the access token to retrieve claims from userinfo
                        var userInfoClient = new UserInfoClient(
                        new Uri(SsoConfigHelper.SellutionStsUserInfoEndpoint),
                        tokenResponse.AccessToken);

                        var userInfoResponse = await userInfoClient.GetAsync();

                        // create new identity
                        var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                        id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);

                        id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                        id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
                        //id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                        id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                        id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

                        LoginCookieHelper.SetUserData(tokenResponse.AccessToken);
                        n.AuthenticationTicket = new AuthenticationTicket(
                            new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
                            n.AuthenticationTicket.Properties);
                    },

                    RedirectToIdentityProvider = n =>
                    {

                        // This ensures that the address used for sign in and sign out is picked up dynamically from the request
                        // this allows you to deploy the app (to Azure Web Sites, for example) without having to change settings.
                        var appBaseUrl = n.Request.Scheme + "://" + n.Request.Host + n.Request.PathBase;
                        n.ProtocolMessage.RedirectUri = appBaseUrl;
                        n.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;

                        // if signing out, add the id_token_hint
                        if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
                        {
                            var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                            if (idTokenHint != null)
                            {
                                n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                            }
                        }

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

Identity server configuration:

class Factory
{
        public static IdentityServerServiceFactory Configure()
        {
            var efConfig = new EntityFrameworkServiceOptions
            {
                ConnectionString = "DefaultConnection",

            };

            // these two calls just pre-populate the test DB from the in-memory config
            ConfigureClients(Clients.Get(), efConfig);
            ConfigureScopes(Scopes.Get(), efConfig);

            var factory = new IdentityServerServiceFactory();

            //var scopeStore = new InMemoryScopeStore(Scopes.Get());
            //factory.ScopeStore = new Registration<IScopeStore>(scopeStore);
            //var clientStore = new InMemoryClientStore(Clients.Get());
            //factory.ClientStore = new Registration<IClientStore>(clientStore);

            factory.CorsPolicyService = new Registration<ICorsPolicyService>(new DefaultCorsPolicyService { AllowAll = true });

            factory.RegisterOperationalServices(efConfig);
            factory.RegisterConfigurationServices(efConfig);

            return factory;
        }


        public static void ConfigureClients(IEnumerable<Client> clients, EntityFrameworkServiceOptions options)
        {
            using (var db = new ClientConfigurationDbContext(options.ConnectionString, options.Schema))
            {
                if (!db.Clients.Any())
                {
                    foreach (var c in clients)
                    {
                        var e = c.ToEntity();
                        db.Clients.Add(e);
                    }
                    db.SaveChanges();
                }
            }
        }

        public static void ConfigureScopes(IEnumerable<Scope> scopes, EntityFrameworkServiceOptions options)
        {
            using (var db = new ScopeConfigurationDbContext(options.ConnectionString, options.Schema))
            {
                if (!db.Scopes.Any())
                {
                    foreach (var s in scopes)
                    {
                        var e = s.ToEntity();
                        db.Scopes.Add(e);
                    }
                    db.SaveChanges();
                }
            }
        }
}

IdentityServer client configuration

public class Clients
{
        public static List<Client> Get()
        {
            return new List<Client>
            {
                 new Client
                 {
                    ClientName = "Resource Owner Flow",
                    ClientId = "resourceowner",
                    ClientSecrets = new List<Secret> {new Secret("vkgk8M4pj".Sha256())},
                    Flow = Flows.ResourceOwner  , //Password authentication
                    PrefixClientClaims = false,
                    AccessTokenType = AccessTokenType.Jwt,
                    AllowedScopes = new List<string>
                    {
                        Constants.StandardScopes.OpenId,
                        Constants.StandardScopes.Profile,
                        Constants.StandardScopes.Email,
                        Constants.StandardScopes.Roles,
                        Constants.StandardScopes.Address,
                        Constants.StandardScopes.AllClaims,
                        Constants.StandardScopes.OfflineAccess,
                        SsoConfigHelper.SellutionApiScope
                    },
                   RequireConsent = false,
                   AllowRememberConsent = true,
                   LogoutSessionRequired = true,

                   RefreshTokenExpiration = TokenExpiration.Absolute,
                   RefreshTokenUsage = TokenUsage.OneTimeOnly,
                   UpdateAccessTokenClaimsOnRefresh = true,
                   AbsoluteRefreshTokenLifetime =(int)TimeSpan.FromDays(1).TotalSeconds
                },

                /////////////////////////////////////////////////////////////
                // MVC OWIN Implicit Client
                /////////////////////////////////////////////////////////////
                new Client
                {
                    ClientName = "Sellution Application",
                    ClientId = "sellutionapp",
                    Flow = Flows.Hybrid,
                    AllowAccessTokensViaBrowser = false,

                    AllowedScopes = new List<string>
                    {
                        Constants.StandardScopes.OpenId,
                        Constants.StandardScopes.Profile,
                        Constants.StandardScopes.Email,
                        Constants.StandardScopes.Roles,
                        Constants.StandardScopes.Address,
                        Constants.StandardScopes.AllClaims,
                        SsoConfigHelper.SellutionApiScope
                    },
                     ClientSecrets = new List<Secret>
                    {
                        new Secret("secret".Sha256())
                    },

                    AccessTokenType = AccessTokenType.Reference,
                    RequireConsent = false,
                    AllowRememberConsent = true,
                    LogoutSessionRequired = true,
                },

            };
        }
}
1

1 Answers

0
votes

You need to sign the user out of the MVC application as well because according to your code you are using Cookie Authentication named Cookies you also have to do a sign out on that authentication scheme when they log out (not deleteing the token in the store).

AuthenticationManager.SignOut("Cookies"); is what your controller action will require for logout.