How to restart the browser keeping the cookies in Behat? - session-cookies

I am trying to test the "remember me" feature using Behat/Mink. Here's my scenario:
Scenario: A user logs in ticking "Remember me". As he closes his browser and visits back the site, he should be automatically logged in
Given I am on "/login"
Then I should see "Site Login"
When I fill in "Username" with "test"
And I fill in "Password" with "test"
And I check "Remember me"
When I press "Login"
Then I should see "Welcome"
When I restart the browser
Then I go to "/login"
Then I should see "Welcome"
Here's the definition for restarting the browser:
/**
* #When /^I restart the browser$/
*/
public function iRestartTheBrowser()
{
$this->getSession()->restart();
}
I have also tried $this->getSession()->reset();
The problem is since the cookies are deleted upon browser restart, the "remember me" feature no longer works. Is there any way of doing a restart in mink without clearing the cookies?

You could get a cookie before restarting the session and set it back afterwards:
$cookie = $session->getCookie('remember_me');
$session->restart();
// I'm not sure if visiting a page before setting a cookie is actually needed
// after restarting the session.
// It's definitely needed when setting a cookie before the first request
// (to set the cookie domain).
$session->visit('/')
$session->setCookie('remember_me', $cookie);

I have solved this problem by adding new function to enable the test to close the browser.
/**
* #When /^I close the browser$/
*/
public function iCloseTheBrowser(){
$this->getSession()->getDriver()->stop();
}
The feature steps look like this:
...
When I close the browser
And I am on the "My" page
Then I should see "Logged: icon
...

One way to do it is to get all cookies before closing and reopening the browser and then setting back all the cookies which have explicit expiration date. The trick is to get cookies using selenium webdriver session and not generic mink session, as the webdriver session returns everything for a cookie (path, expiry date, etc.) rather than just the values. The code in my context looks like this:
/** #When I restart the browser */
public function iRestartTheBrowser()
{
/** #var Selenium2Driver $driver */
$driver = $this->getSession()->getDriver();
/** #var \WebDriver\Session $session */
$seleniumSession = $driver->getWebDriverSession();
$cookies = $seleniumSession->getAllCookies();
$minkSession = $this->getSession();
$minkSession->restart();
//The following is necessary - as the cookies can only be set after
//you're already on the domain - this can be any page, even an error page
$minkSession->visit($this->getMinkParameter('base_url'));
$seleniumSession = $driver->getWebDriverSession();
foreach ($cookies as $cookie) {
if (isset($cookie['expiry'])) {
$seleniumSession->setCookie($cookie);
}
}
}

Related

Aspnet core cookie [Authorize] not redirecting on ajax calls

In an asp.net core 3.1 web app with cookie-based authorization I have created a custom validator which executes on the cookie authorization's OnValidatePrincipal event. The validator does a few things, one of those is check in the backend if the user has been blocked. If the user has been blocked, The CookieValidatePrincipalContext.RejectPrincipal() method is executed and the user is signed out using the CookieValidatePrincipalContext.HttpContext.SignOutAsyn(...) method, as per the MS docs.
Here is the relevant code for the validator:
public static async Task ValidateAsync(CookieValidatePrincipalContext cookieValidatePrincipalContext)
{
var userPrincipal = cookieValidatePrincipalContext.Principal;
var userService = cookieValidatePrincipalContext.GetUserService();
var databaseUser = await userService.GetUserBySidAsync(userPrincipal.GetSidAsByteArray());
if (IsUserInvalidOrBlocked(databaseUser))
{
await RejectUser(cookieValidatePrincipalContext);
return;
}
else if (IsUserPrincipalOutdated(userPrincipal, databaseUser))
{
var updatedUserPrincipal = await CreateUpdatedUserPrincipal(userPrincipal, userService);
cookieValidatePrincipalContext.ReplacePrincipal(updatedUserPrincipal);
cookieValidatePrincipalContext.ShouldRenew = true;
}
}
private static bool IsUserInvalidOrBlocked(User user)
=> user is null || user.IsBlocked;
private static async Task RejectUser(CookieValidatePrincipalContext context)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
And here is the setup for the cookie-based authorization:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(co =>
{
co.LoginPath = #$"/{ControllerHelpers.GetControllerName<AuthenticationController>()}/{nameof(AuthenticationController.Login)}";
co.LogoutPath = #$"/{ControllerHelpers.GetControllerName<AuthenticationController>()}/{nameof(AuthenticationController.Logout)}";
co.ExpireTimeSpan = TimeSpan.FromDays(30);
co.Cookie.SameSite = SameSiteMode.Strict;
co.Cookie.Name = "GioBQADashboard";
co.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = UserPrincipalValidator.ValidateAsync
};
co.Validate();
});
This actually gets called and executed as expected and redirects the user to the login page when they navigate to a new page after having been blocked.
Most of the views have ajax calls to api methods that execute on a timer every 10 seconds. For those calls the credentials also get validated and the user gets signed out. However, after the user has been signed out, a popup asking for user credentials appears on the page:
If the user doesn't enter their credentials and navigate to another page, they get taken to the login page as expected.
If they do enter their credentials, they stay logged in, but their identity appears to be their windows identity...
What is going on here? What I would really want to achieve is for users to be taken to the login page for any request made after they have been signed out.
I have obviously misconfigured something, so that the cookie-based authorization doesn't work properly for ajax requests, but I cannot figure out what it is.
Or is it the Authorization attribute which does not work the way I'm expecting it to?
The code lines look good to me.
This login dialog seems to be the default one for Windows Authentication. Usually, it comes from the iisSettings within the launchSettings.json file. Within Visual Studio you'll find find the latter within your Project > Properties > launchSettings.json
There set the windowsAuthentication to false.
{
"iisSettings": {
"windowsAuthentication": false,
}
}

Are Session Fixation Attacks in MVC 5 still an issue

I've been reading a lot about session fixation attacks and the most popular solutions I've come across are changing the SessionID when user logs in and creating an additional cookie using a GUID to verify the user "belongs" to the SessionID.
My question is this: Isn't it enough to just delete the SessionID cookie (ASP.NET_SessionID) to ensure a new SessionID is generated?
In MVC 5, when the user logs in an additional encrypted user claims cookies is created (AspNet.ApplicationCookie) which Identity uses to authenticate the user upon each request. The additional "GUID cookie" seems unnecessary.
I’m originally a .NET desktop application developer writing my first MVC app and the learning curve has been a bit steep… although refreshingly enjoyable.
Thanks for any help.
Let me try to explain the issue and the solution by using comparisons between desktop and web apps (both in .Net)
When you start your desktop app, the first thing the app shows is a login screen, after which your access to the UI is granted. Now, each time the app's exe is started, it writes the "RunID" to a text file and shows the login screen. The RunID is how the rest of your usage of the app is going to be tracked/correlated.
Assume for a second that the file was on C:\RunID.txt.
An attacker (hacker) can start the exe (without logging in) on Machine1 and copy the contents of C:\RunID.txt to Machine2. Now as soon as you log in on Machine1, the RunID token from Machine1 will also work on Machine2, this is called session fixation.
The ideal way to fix it is to ABANDON the pre-authentication token, and issue a NEW Post-Authentication token. So, you would get a new Token after authentication (or in your case, an additional GUID) which will NOT EXIST on Machine2 and hence provide a level of security in addition to the RunID random token (Session ID)
Let me know if you'd like further explaination, but that is why even in MVC, you should abandon the previous session and create a new session post-auth to avoid session fixation, as a compensating control, you can add a GUID cookie too correspond with the Session ID cookie.
You can do this to avoid that situation:
SessionIDManager Manager = new SessionIDManager();
string NewID = Manager.CreateSessionID(Context);
string OldID = Context.Session.SessionID;
bool redirected = false;
bool IsAdded = false;
Manager.SaveSessionID(Context, NewID, out redirected, out IsAdded);
Response.Write("Old SessionId Is : " + OldID);
if (IsAdded)
{
Response.Write("<br/> New Session ID Is : " + NewID);
}
else
{
Response.Write("<br/> Session Id did not saved : ");
}
Support link:
Link

Why does anonymous user get redirected to expiredsessionurl by Spring Security

I'm really trying to understand how Spring Security works, but I'm a bit lost at the moment. Here's the simple scenario:
User visits the website home page but doesn't log in
SecurityContextPersistenceFilter logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter populates SecurityContextHolder with an anonymous token
A session is created with ID = C2A35ED5A41E29865FF53162B0024D52
User lets the page sit idle until the session times out
User clicks on the About page (or home page again)
SecurityContextPersistenceFilter again logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter again populates SecurityContextHolder with an anonymous token
SessionManagementFilter logs that requested session ID C2A35ED5A41E29865FF53162B0024D52 is invalid
SessionManagementFilter logs that it is starting a new session and redirecting to /invalidsession
These pages are configured to .authorizeRequests().antMatchers("/","/home","/about").permitAll(). I have the invalid session option turned on to handle authenticated users: .sessionManagement().invalidSessionUrl("/errors/invalidSession"). If I comment out that option, then everything described above is exactly the same EXCEPT for step #10 - SessionManagementFilter sees that the requested session ID is invalid (#9) but does NOT start a new session and perform the redirect (#10).
WHY? What can I do to keep the invalid session option but correctly handle anonymous users, i.e., not be redirected? Or is that just not possible and I'll have to handle authenticated users separately? I'd be very grateful if anyone can help me understand what's happening here and point me in a direction to solve this. Let me know if you need to see my full http configuration.
EDIT
I ran a series of tests with anonymous and registered (authenticated) users. If .sessionManagement().invalidSessionUrl("/errors/invalidSession") is enabled then both types of users will eventually arrive at the error page. Authenticated users with RememberMe unchecked are the same as anon users. If RememberMe is checked, then the error page appears once RememberMe times out.
If I disable the invalid session option, no users ever get the error page (which makes sense). Both types of users can browse public pages as long as they want and authenticated users will be asked to log in after the session or RememberMe expires.
If you're interested the code involved here is in SessionManagementFilter
if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
If .sessionManagement().invalidSessionUrl is enabled the default method SimpleRedirectInvalidSessionStrategy is called, which executes this piece of code:
if (createNewSession) {
request.getSession();
}
redirectStrategy.sendRedirect(request, response, destinationUrl);
The createNewSession boolean can be set through setCreateNewSession(boolean createNewSession), which is described as:
Determines whether a new session should be created before redirecting (to avoid possible looping issues where the same session ID is sent with the redirected request). Alternatively, ensure that the configured URL does not pass through the SessionManagementFilter.
So, it looks to me like .sessionManagement().invalidSessionUrl works best for sites where all pages are authenticated. The options I'm looking at are a custom filter placed before the SessionManagementFilter that checks the page access and turns 'createNewSession' on/off as needed or turning off the invalid session option and handling it elsewhere for authenticated pages (?). I also stumbled across <%# page session=“false” %> in this SO question - Why set a JSP page session = “false” directive? - which I'm going to look into further. Being so new to Spring Security I don't have a good sense of the best practice for handling this situation correctly. Any help would be appreciated.
OK, so I've spent the last couple of weeks digging around in Spring Security trying to understand how it all fits together. I'm still learning, but for this particular situation I found two approaches that work.
The obvious one is to just bypass security for public pages like this:
#Override
public void configure(WebSecurity web) throws Exception
{
web
.ignoring()
.antMatchers("/", "/home", "/about", "/login**", "/thankyou", "/user/signup**", "/resources/**")
;
}
I still don't know enough about web security in general to know if this is an acceptable approach or not, but it allows anonymous users to browse the site w/o ever getting an invalid session error.
The harder solution (for a Java and Spring noob like me) is based upon these SO questions:
Spring security invalid session redirect
How to set a custom invalid session strategy in Spring Security
The default SimpleRedirectInvalidSessionStrategy class is final which meant I had to create basically a copy of that class (not sure how good an idea that is). You can't use a session attribute because the session has been destroyed by the time it gets to this strategy so I created a helper class for a session cookie called authUser (I can post the class if anyone wants to see it). The cookie is created or updated in the LoginSuccessHandler or RememberMeSuccessHandler and it indicates if the user is anonymous or authenticated:
authCookie.setCookie(request, response, "anonymousUser");
or
authCookie.setCookie(request, response, authentication.getName());
I'm currently using the actual login only for testing purposes - it will ultimately be just a simple yes/no indicator of some sort. CustomLogoutSuccessHandler resets it to anonymousUser
The invalid session method looks like this:
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String url = destinationUrl;
//reset context default value
redirectStrategy.setContextRelative(false);
if (authCookie.isCurrentCookieAnonymous()) {
//pass the URL originally requested by the anonymous user
url = request.getRequestURI();
//the URL needs to have the context removed
redirectStrategy.setContextRelative(true);
}
//always revert to anonymous user
authCookie.setCookie(request, response, "anonymousUser");
logger.debug("Starting new session (if required) and redirecting to '" + url + "'");
if (createNewSession)
request.getSession();
redirectStrategy.sendRedirect(request, response, url);
}
Again, I can post the full class if requested.
The SecurityConfig class includes the following:
#Bean
public SessionManagementBeanPostProcessor sessionManagementBeanPostProcessor() {
return new SessionManagementBeanPostProcessor();
}
protected static class SessionManagementBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof SessionManagementFilter) {
SessionManagementFilter filter = (SessionManagementFilter) bean;
filter.setInvalidSessionStrategy(new RedirectInvalidSession("/errors/invalidSession"));
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
My testing so far has been successful for both anonymous and authenticated users, but this approach has not been production tested.

How to clear apache shiro session?

I used apache shiro session for the authentication and authorization.
Am able to login with different user and permission and roles but actual problem is whenever i call a signOut function looks like shiro session is not getting wiped off.
The evident for this is whenever i clicked logout it comes main screen and if i use browser back button i can go back for the last screen.
My signOut function looks like this
// Log the user out of the application.
SecurityUtils.subject?.logout()
webRequest.getCurrentRequest().session = null
session.invalidate()
// For now, redirect back to the home page.
redirect(uri: "/")
Any help on this really appreciated struggling for this from past 2 days
This works for me with version version 1.1.4 of the shiro plugin.
def logOut() {
SecurityUtils.subject?.logout()
redirect(uri: "/")
}
This is due to browser cache. You can configure to reset your browser cache in ShiroSecurityFilters file.
class ShiroSecurityFilters {
def filters = {
requestHeadersFilter(controller: '*', action: '*') {
after = {
response.setHeader("Pragma", "no-cache")
response.setDateHeader("Expires", 0)
response.setHeader("Cache-Control", "no-cache")
response.addHeader("Cache-Control", "no-store")
}
}

Grails Spring Security: Logging in with a target URL skips post authentication workflow

In my grails app I have customized the post authorization workflow by writing a custom auth success handler (in resources.groovy) as shown below.
authenticationSuccessHandler (MyAuthSuccessHandler) {
def conf = SpringSecurityUtils.securityConfig
requestCache = ref('requestCache')
defaultTargetUrl = conf.successHandler.defaultTargetUrl
alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault
targetUrlParameter = conf.successHandler.targetUrlParameter
useReferer = conf.successHandler.useReferer
redirectStrategy = ref('redirectStrategy')
superAdminUrl = "/admin/processSuperAdminLogin"
adminUrl = "/admin/processAdminLogin"
userUrl = "/admin/processUserLogin"
}
As you can from the last three lines in the closure above, depending on the Role granted to the logging in User I am redirecting her to separate actions within the AdminController where a custom UserSessionBean is created and stored in the session.
It works fine for a regular login case which in my app is like so:
User comes to the app via either http://localhost:8080/my-app/ OR http://localhost:8080/my-app/login/auth
She enters her valid login id and password and proceeds.
The app internally accesses MyAuthSuccessHandler which redirects to AdminController considering the Role granted to this User.
The UserSessionBean is created and stored it in the session
User is taken to the app home page
I have also written a custom MyUserDetailsService by extending GormUserDetailsService which is correctly accessed in the above flow.
PROBLEM SCENARIO:
Consider a user directly accessing a protected resource (in this case the controller is secured with #Secured annotation) within the app.
User clicks http://localhost:8080/my-app/inbox/index
App redirects her to http://localhost:8080/my-app/login/auth
User enters her valid login id and password
User is taken to http://localhost:8080/my-app/inbox/index
The MyAuthSuccessHandler is skipped entirely in this process and hence my UserSessionBean is not created leading to errors upon further use in places where the UserSessionBean is accessed.
QUESTIONS:
In the problem scenario, does the app skip the MyAuthSuccessHandler because there is a target URL for it to redirect to upon login?
Can we force the process to always pass through MyAuthSuccessHandler even with the target URL present?
If the answer to 2 is no, is there an alternative as to how and where the UserSessionBean can still be created?
You can implement a customized eventListener to handle the post-login process, without disrupting the original user requested url.
In config.groovy, insert a config item:
grails.plugins.springsecurity.useSecurityEventListener = true
In you resources.groovy, add a bean like this:
import com.yourapp.auth.LoginEventListener
beans = {
loginEventListener(LoginEventListener)
}
And create a eventListener in src/groovy like this:
package com.yourapp.auth
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
import org.springframework.web.context.request.RequestContextHolder as RCH
class LoginEventListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent> {
//deal with successful login
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
User.withTransaction {
def user = User.findByUsername(event.authentication.principal.username)
def adminRole = Role.findByAuthority('ROLE_ADMIN')
def userRole = Role.findByAuthority('ROLE_USER')
def session = RCH.currentRequestAttributes().session //get httpSession
session.user = user
if(user.authorities.contains(adminRole)){
processAdminLogin()
}
else if(user.authorities.contains(userRole)){
processUserLogin()
}
}
}
private void processAdminLogin(){ //move admin/processAdminLogin here
.....
}
private void processUserLogin(){ //move admin/processUserLogin here
.....
}
}
Done.
1) Yes, because it is an "on-demand" log in.
2) Yes, you can set it to always use default. The spring security plugin has a setting for it "successHandler.alwaysUseDefault" change that to true it defaults to false.
Also if you need more details check out the spring docs look for the Setting a Default Post-Login Destination section.
3) If you want to still create the user session bean and then redirect to the original URL you have two options create the bean in an earlier filter or expose the needed data via a custom UserDetailsService. Personally I would go the route of a custom details service.

Resources