I have a requirement to close a session held by a JSESSIONID
cookie when the user closes the browser window.
This cookie is set at server-side by Spring Security:
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration {
@Configuration
public static class ApplicationApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) {
http
.antMatcher("/**")
.csrf()
.disable() // Disable Cross Site Request Forgery Protection
.formLogin() // Enable form based/cookie based/session based login
.loginPage(LOGIN_PAGE) // Set our custom login page
.and()
.authorizeRequests()
.antMatchers("/login", "/logout")
.permitAll() // Allow anyone to access the login and logout page
.anyRequest()
.authenticated() //All other request require authentication
.and()
.logout()
.deleteCookies("JSESSIONID") // Delete JSESSIONID cookie on logout
.clearAuthentication(true) // Clean authentication on logout
.invalidateHttpSession(true); // Invalidate Http Session on logout
}
}
}
At client side I have AngularJS 1.7 and TypeScript and I am already capturing the beforeunload
event by setting up @module.ts
module app.myapp {
import IModule = angular.IModule;
import IStateProvider = angular.ui.IStateProvider;
import IUrlRouterProvider = angular.ui.IUrlRouterProvider;
import LockService = lib.common.LockService;
export class MyApp {
static NAME = 'app.myapp';
static module(): IModule {
return angular.module(MyApp.NAME);
}
}
angular.module(MyApp.NAME, ['ui.router'])
.config([
'$stateProvider', '$urlRouterProvider',
($stateProvider: IStateProvider, $urlRouterProvider: IUrlRouterProvider) => {
$urlRouterProvider.when('', '/home');
$stateProvider.state('base', {
url: '',
abstract: true,
template: '<ui-view/>',
resolve: {
initEventListenerForGlobalUnlock: [
LockService.ID,
(lockService: LockService) => lockService.initListenerForGlobalUnlock()
]
}
});
$stateProvider.state('personalProfile', {
url: '/profile',
parent: 'base',
component: PersonalProfile.ID
});
}
]);
}
and then for my LockService implementation:
module lib.common {
import IWindowService = angular.IWindowService;
import IRootScopeService = angular.IRootScopeService;
export class LockService {
static ID = 'lockService';
static $inject: string[] = ['$window', '$rootScope'];
constructor(
private readonly $window: IWindowService,
private readonly $rootScope: IRootScopeService
) { }
private readonly globalUnlockHandler = (ev: BeforeUnloadEvent) => {
// This does not work with HttpOnly cookies
document.cookie = 'JSESSIONID=;path=/;domain=' + window.location.hostname + ';expires=Thu, 01 Jan 1970 00:00:01 GMT';
this.$rootScope.$digest();
return null;
}
/**
* This is called upon app initialisation,
* an event listener for browser close is registered,
* which will use an API call to unlock all locked sections before exiting
*/
initListenerForGlobalUnlock(): void {
this.$window.addEventListener('beforeunload', this.globalUnlockHandler);
}
}
LibCommon.module().service(LockService.ID, LockService);
}
Currently, there is a log-out funtionality based on redirecting the page by doing this.$window.location.href = 'logout'
But what I want is to delete the session cookie (or else invalidate somehow the session) when the browser window is closed by clicking [x] so that even if the user comes back to the same page by pasting the URL in the address bar he is asked to log in again.
The problem is that JSESSIONID
cookie is set to be HttpOnly and therefore cannot be deleted from JavaScript. And I do not know how to tell Spring Security on server side to invalidate the session.