{"action":"created","issue":{"url":"https://api.github.com/repos/IdentityServer/IdentityServer3/issues/1400","labels_url":"https://api.github.com/repos/IdentityServer/IdentityServer3/issues/1400/labels{/name}","comments_url":"https://api.github.com/repos/IdentityServer/IdentityServer3/issues/1400/comments","events_url":"https://api.github.com/repos/IdentityServer/IdentityServer3/issues/1400/events","html_url":"https://github.com/IdentityServer/IdentityServer3/issues/1400","id":80801058,"number":1400,"title":"Single Signout/Logout From Multiple Clients/Relying Parties","user":{"login":"parkinsona","id":8508456,"avatar_url":"https://avatars.githubusercontent.com/u/8508456?v=3","gravatar_id":"","url":"https://api.github.com/users/parkinsona","html_url":"https://github.com/parkinsona","followers_url":"https://api.github.com/users/parkinsona/followers","following_url":"https://api.github.com/users/parkinsona/following{/other_user}","gists_url":"https://api.github.com/users/parkinsona/gists{/gist_id}","starred_url":"https://api.github.com/users/parkinsona/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/parkinsona/subscriptions","organizations_url":"https://api.github.com/users/parkinsona/orgs","repos_url":"https://api.github.com/users/parkinsona/repos","events_url":"https://api.github.com/users/parkinsona/events{/privacy}","received_events_url":"https://api.github.com/users/parkinsona/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/IdentityServer/IdentityServer3/labels/question","name":"question","color":"cc317c"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":10,"created_at":"2015-05-26T05:31:44Z","updated_at":"2015-05-29T05:55:18Z","closed_at":null,"body":"Hi,\r\n\r\nIn similar issues, you've said the solution for checking session state is to do it via javascript.\r\nI have looked at the JS Implicit Client sample and can see your implementation for single sign out working. \r\n\r\nIf I am using an MVC implicit client, is it safe to remove the cookie via javascript from the  mgr.addOnTokenRemoved event handler?"},"comment":{"url":"https://api.github.com/repos/IdentityServer/IdentityServer3/issues/comments/106698914","html_url":"https://github.com/IdentityServer/IdentityServer3/issues/1400#issuecomment-106698914","issue_url":"https://api.github.com/repos/IdentityServer/IdentityServer3/issues/1400","id":106698914,"user":{"login":"parkinsona","id":8508456,"avatar_url":"https://avatars.githubusercontent.com/u/8508456?v=3","gravatar_id":"","url":"https://api.github.com/users/parkinsona","html_url":"https://github.com/parkinsona","followers_url":"https://api.github.com/users/parkinsona/followers","following_url":"https://api.github.com/users/parkinsona/following{/other_user}","gists_url":"https://api.github.com/users/parkinsona/gists{/gist_id}","starred_url":"https://api.github.com/users/parkinsona/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/parkinsona/subscriptions","organizations_url":"https://api.github.com/users/parkinsona/orgs","repos_url":"https://api.github.com/users/parkinsona/repos","events_url":"https://api.github.com/users/parkinsona/events{/privacy}","received_events_url":"https://api.github.com/users/parkinsona/received_events","type":"User","site_admin":false},"created_at":"2015-05-29T05:55:18Z","updated_at":"2015-05-29T05:55:18Z","body":"I think I've got a solution sorted for MVC.\r\nI'll try my best to outline it in a way that makes sense.\r\n\r\nIn my startup.cs I added 2 new claims during the SecurityTokenValidated handler. I also made sure I kept the \"Exp\" claim so I could reference the expiry date of the token.\r\n- SessionStateClaim (.sessionState)\r\n- CheckSessionIFrameClaim (.checkSessionIFrame)\r\n\r\nThis is the securityTokenValidated handler from startup.cs\r\n``` c#\r\n SecurityTokenValidated = async n =>\r\n                                   {\r\n                                       var id = n.AuthenticationTicket.Identity;\r\n\r\n\r\n                                       //Add the id_token claim for post logout redirect\r\n                                       id.AddClaim(new Claim(Constants.IdTokenClaim, n.ProtocolMessage.IdToken));\r\n\r\n                                       \r\n                                       var checkSessionIFrame = n.AuthenticationTicket.Properties.Dictionary[Constants.CheckSessionIFrameClaim];\r\n                                       var sessionState = n.ProtocolMessage.SessionState;\r\n\r\n                                       //Assume that the SessionStateClaim should be populated. If it isn't then there has been manual fiddling of cookies.\r\n                                       if (string.IsNullOrWhiteSpace(sessionState))\r\n                                       {\r\n                                           throw new Exception(\"No session state was returned from identity server. There is an issue with the cookies. Please clear all idsvr cookies and relogin.\");\r\n                                       }\r\n                                     \r\n                                     \r\n\r\n                                       id.AddClaim(new Claim(Constants.SessionStateClaim, sessionState));\r\n                                       id.AddClaim(new Claim(Constants.CheckSessionIFrameClaim, checkSessionIFrame));\r\n\r\n                                      \r\n                                       //Change the Name Claim Type\r\n                                       id = new ClaimsIdentity(id, null, id.AuthenticationType, Constants.NameIdClaim, id.RoleClaimType);\r\n\r\n                                       n.AuthenticationTicket = new AuthenticationTicket(\r\n                                           id,\r\n                                           n.AuthenticationTicket.Properties);\r\n                                   },\r\n```\r\n\r\nI created this javascript (idsvr_checksession.js) and added it to my bundles.\r\n``` c#\r\n//Checks there is a current user session. If there is, it will set the iframe to the check_session page\r\n//The check session page will perform the session check on identity server\r\nfunction checkSessionState(identityConfig) {\r\n\r\n\r\n    if (identityConfig.check_session_iFrame && identityConfig.session_state) {\r\n        //Pass the session_State, check session iframe, idsvr client id, expiry date and check frequency to the check session page.\r\n        document.getElementById(identityConfig.rpIframe).src = settings.baseUrl + \"/Content/HTML/check_session.html#\" +\r\n                \"session_state=\" + identityConfig.session_state +\r\n                \"&check_session_iframe=\" + identityConfig.check_session_iFrame +\r\n                \"&client_id=\" + identityConfig.client_id +\r\n        \"&session_expires_at=\" + identityConfig.session_expires_at +\r\n        \"&frequency=\" + identityConfig.frequency;\r\n\r\n\r\n        //Add handler for when identity server is logged out\r\n        window.onmessage = function (e) {\r\n            if (e.origin === window.location.protocol + \"//\" + window.location.host && e.data === \"changed\") {\r\n                console.log(\"logged out\");\r\n\r\n                //signout the current RP session via ajax\r\n                $.ajax({\r\n                    type: \"POST\",\r\n                    url: identityConfig.rpSignoutUrl,\r\n                    data: param = \"\",\r\n                    contentType: \"application/json; charset=utf-8\",\r\n                    dataType: \"json\",\r\n                    success: signoutSuccessFunc(identityConfig.modalSelector),\r\n                    error: signoutErrorFunc,\r\n                    beforeSend: signoutBeginFunc,\r\n                });\r\n\r\n            }\r\n\r\n        }\r\n\r\n\r\n    }\r\n    else {\r\n\r\n        document.getElementById(identityConfig.rpIframe).src = \"about:blank\";\r\n    }\r\n\r\n\r\n}\r\n\r\n//Display modal when signout of RP was successful. \r\n// modalSelector - The jquery selector for the modal.\r\nfunction signoutSuccessFunc(data, textStatus, jqXH) {\r\n  \r\n        console.log(\"Identity Server session has expired and local cookie has been expired\");\r\n        console.log(\"Data: \" + data);\r\n        console.log(\"status: \" + status);        \r\n        displaySignOutNotification();\r\n\r\n}\r\n\r\n\r\nfunction signoutErrorFunc() {\r\n    console.log(\"Error occurred when trying to remove local cookie from idsvr handler\");\r\n}\r\n\r\nfunction signoutBeginFunc() {\r\n    console.log(\"Attempting to expire local cookie\");\r\n}\r\n```\r\n\r\nIm my _layout.cshtml I added the following iframe & script after my bundles.\r\nThe iframe is used to house the check_session html which is called in the script. As I have a few RPs to implement this for, I wanted to make the setup in the layout as simple as possible. The config item does most of the work & requires:\r\n - client_id - The RP client id as configured in idsvr\r\n - session_state - Session state that is retrieved from the claim we created earlier\r\n - check_session_iFrame - The iframe on idsvr used for checking session, also from a claim we created earlier.\r\n - session_expires_at - Date the RP session will expire. After this date has passed, the session checking stops locally to avoid unnecessary checks. The next call via owin will do a check for us, so no point wasting traffic.\r\n - frequency - How often to check identity server for a current session in milliseconds\r\n - rpSignoutUrl - This is the path of your controller that will kill your local RP cookie. We remove the RP cookie to ensure no more requests can be made using it.\r\n - rpIframe - The id of the iframe in the RP _layout.cshtml\r\n\r\n\r\n``` c#\r\n <iframe id=\"rp\" style=\"display:none;\"></iframe>\r\n\r\n    <script type=\"text/javascript\">\r\n        //Add a function to retrieve the baseUrl and add it to a settings object. This can be used on all javascripts.\r\n        var settings = {\r\n            baseUrl: '@Url.Content(\"~\")'\r\n        };\r\n\r\n        //Setup configuration for Identity Server Session Checking\r\n        var identityCheckConfig = {\r\n            client_id: \"identitymanager_implicitclient\",\r\n            session_state: '@xxx.Extensions.ClaimsHelper.GetSessionState(User)',\r\n            check_session_iFrame: '@xxx.Logic.Extensions.ClaimsHelper.GetCheckSessionIFrame(User)',\r\n            session_expires_at: '@Newtonsoft.Json.JsonConvert.SerializeObject(xxx.Logic.Extensions.ClaimsHelper.GetSessionExpiry(User)).Replace(\"\\\"\", \"\")',\r\n            frequency: 2000,\r\n            rpSignoutUrl: settings.baseUrl + 'Account/SignoutFromIdentity',\r\n            rpIframe: \"rp\"\r\n        };\r\n\r\n\r\n        //Handler to display the sign out notification \r\n        function displaySignOutNotification() {\r\n            $(\"#sessionModal\").modal();\r\n        };\r\n\r\n        //Call check session state to start the checking\r\n        checkSessionState(identityCheckConfig);\r\n\r\n    </script>\r\n```\r\n\r\nNext we have the signout method in our account controller that will sign us only out of the RP.\r\n``` c#\r\n [OverrideAuthorization]\r\n        public ActionResult  SignoutFromIdentity(string redirectUri)\r\n        {\r\n\r\n            Request.GetOwinContext().Authentication.SignOut(\"Cookies\");\r\n            return Json(\"Signed out of RP\");\r\n        }\r\n```\r\n\r\nLastly, we have an update check_session.html page with some extra functionality.\r\n``` c#\r\n<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <title>RP Check Session IFrame</title>\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\r\n</head>\r\n<body>\r\n    <h2>check session frame</h2>\r\n    <iframe id=\"op\"></iframe>\r\n    <script>\r\n        \r\n        /* Checks for a current session in identity server. Requires the following parameters in the query string\r\n         * - session_state - Identitifer of the session in identity server. This is used to check for a valid session in idsvr. \r\n         * - check_session_iframe - The iframe in identity server to check. This is originally returned from the authentication request.\r\n         * - client_id - The relying party client identitifer. Must match the config in idsvr.\r\n         * - session_expires_at - The datetime when the local (RP) session is due to expire. After this time no checks will be made to idsvr to avoid unneccesary traffic.\r\n         * - frequency - How often to check the session in identity server in milliseconds.\r\n         */\r\n\r\n\r\n        console.log(\"check session: \" + window.location);\r\n        if (window.location.hash) {\r\n            \r\n            var hash = window.location.hash.substr(1);\r\n            var params = hash.split('&').reduce(function (curr, item) {\r\n                var parts = item.split('=');\r\n                curr[parts[0].trim()] = parts[1].trim();\r\n                return curr;\r\n            }, {});\r\n\r\n            console.log('check_session_frame', params);\r\n\r\n            if (!params.session_state) {\r\n                console.error(\"No session_state passed to RP session frame\");\r\n            }\r\n            else if (!params.check_session_iframe) {\r\n                console.error(\"No check_session_iframe passed to RP session frame\");\r\n            }\r\n            else if (!params.client_id) {\r\n                console.error(\"No client_id passed to RP session frame\");\r\n            }\r\n            else if (!params.session_expires_at) {\r\n                console.error(\"No session_expires_at passed to RP session frame\");\r\n            }\r\n            else if (!params.frequency) {\r\n                console.error(\"No frequency passed to RP session frame\");\r\n            }\r\n            else {\r\n                console.log(\"session expires at: \" + params.session_expires_at)\r\n                \r\n                \r\n                \r\n                var idx = params.check_session_iframe.indexOf(\"/\", params.check_session_iframe.indexOf(\"//\") + 2);\r\n                var op_origin = params.check_session_iframe.substr(0, idx);\r\n\r\n                var frame = document.getElementById(\"op\");\r\n                frame.onload = function () {\r\n                    var op = frame.contentWindow;\r\n                    var timer = window.setInterval(function () {\r\n                        var currDate = new Date();\r\n                        var expiryDate = new Date(params.session_expires_at);\r\n                        if (currDate > expiryDate)\r\n                        {                            \r\n                            //when the local session expires, stop checking to see if the user is active in idsvr.\r\n                            //The local cookie is expired, so on the next server request the middleware will check the authentication anyway.\r\n                            console.log(\"Local session has expired, we will stop checking the session in idsvr\");\r\n                            window.clearInterval(timer);\r\n                        }\r\n                        else{\r\n                            op.postMessage(params.client_id + \" \" + params.session_state, op_origin);\r\n                        }\r\n                    }, params.frequency);\r\n\r\n                    window.addEventListener(\"message\", function (e) {\r\n                        if (e.origin === op_origin) {\r\n                            console.log(\"session state\", e.data);\r\n                            \r\n                            if (e.data === \"changed\" || e.data === \"error\" ) {\r\n                                window.clearInterval(timer);\r\n                            }\r\n\r\n                            if (e.data === \"changed\") {\r\n                                // do prompt=none w/id_token_hint to OP\r\n                                // check result for same sub\r\n                                // if same, update local id_token_hint\r\n                                console.log(\"Sending message back to parent\");\r\n                                window.parent.postMessage(\"changed\", window.location.protocol + \"//\" + window.location.host);\r\n                            }\r\n                        }\r\n                    });\r\n                };\r\n                frame.src = params.check_session_iframe;\r\n            }\r\n        }\r\n    </script>\r\n</body>\r\n</html>\r\n\r\n```\r\n\r\n\r\n\r\n\r\n\r\n"}}