Session or cookie confusion - session

I've seen in some websites that user signed in into their accounts and then closed the browser.
After closed and re-opened the browser and their accounts are still signed in.
But some websites, cannot do like that.
I'm confused that it's considered session or cookie?
If I want my website to be signed in like that, do I have to set session.setMaxInactiveInterval() or cookie.setMaxAge()?

* This answer has serious flaws, see comments. *
Your question is about session tracking.
[PART 1] : SESSION OBJECT
HTTP-request are processed separately, so in order to keep information between each request (for instance, information about the user), a session object has to be created on server-side.
Some websites doesn't need a session at all. A website where users can't modify any content won't have to manage a session (for instance, an online CV). You won't need any cookie or session on such a website.
Create a session :
In a servlet, use the method request.getSession(true) from the HttpServletRequest object to create a new HttpSession object. Note that if you use request.getSession(false), null will be returned if the session has not already been created. Look at this answer for more details.
Set / Get attributes :
The purpose of a session is to keep information on server-side between each request. For instance, keeping the user's name :
session.setAttribute("name","MAGLEFF");
// Cast
String name = (String) session.getAttribute("name");
Destroy a session :
A session will be automatically destroyed if kept inactive too much time. Look at this answer for more details. But you can manually force the session to be destroyed, in the case of a logout action for example :
HttpSession session = request.getSession(true);
session.invalidate();
[PART 2] : So... join the dark side, we have COOKIES ?
Here comes the cookies.
JSESSIONID :
A JSESSIONID cookie is created on the user's computer each time a session is created with request.getSession(). Why? Because each session created on server side has an ID. You can't access another user's session, unless you don't have the right ID. This ID is kept in JSESSIONID cookie, and allow the user to find his information. Look at this answer for more details !
When does a JSESSIONID get deleted ?
JSESSIONID doesn't have an expiration date : it's a session cookie. As all session cookies, it will be deleted when the browser is closed. If you use the basic JSESSIONID mechanism, then the session will become unreachable after you close and re-open the browser, because the JSESSIONID cookie is deleted.
Note that the session is unreachable by the client, but is still running on server-side. Setting a MaxInactiveInterval allows the server to automatically invalidate the session when it has been inactive for too long.
Evil destruction of JSESSIONID
Just for fun, one day I found this code on a project. It was used to invalidate the session by deleting the JSESSIONID cookie with javascript :
<SCRIPT language="JavaScript" type="text/javascript">
function delete_cookie( check_name ) {
// first we'll split this cookie up into name/value pairs
// note: document.cookie only returns name=value, not the other components
var a_all_cookies = document.cookie.split( ';' );
var a_temp_cookie = '';
var cookie_name = '';
var cookie_value = '';
var b_cookie_found = false; // set boolean t/f default f
// var check_name = 'JSESSIONID';
var path = null;
for ( i = 0; i < a_all_cookies.length; i++ )
{
// now we'll split apart each name=value pair
a_temp_cookie = a_all_cookies[i].split( '=' );
// and trim left/right whitespace while we're at it
cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');
// alert (cookie_name);
// if the extracted name matches passed check_name
if ( cookie_name.indexOf(check_name) > -1 )
{
b_cookie_found = true;
// we need to handle case where cookie has no value but exists (no = sign, that is):
if ( a_temp_cookie.length > 1 )
{
cookie_value = unescape( a_temp_cookie[1].replace(/^\s+|\s+$/g, '') );
document.cookie = cookie_name + "=" + cookie_value +
";path=/" +
";expires=Thu, 01-Jan-1970 00:00:01 GMT";
// alert("cookie deleted " + cookie_name);
}
}
a_temp_cookie = null;
cookie_name = '';
}
return true;
}
// DESTROY
delete_cookie("JSESSIONID");
</SCRIPT>
Give another look to this answer. With JavaScript, JSESSIONID can be read, modified, have it's session lost or hijacked.
[PART 3] : KEEPING A SESSION AFTER CLOSING YOUR BROWSER
After closed and re-opened the browser and their accounts are still
signed in.
But some websites, cannot do like that.
I'm confused that it's considered session or cookie??
It's cookie.
We saw that when the JSESSIONID session cookie has been deleted by the web browser, the session object on server-side is lost. There is no way to access it again without the right ID.
If I want my website to be signed in like that, do I have to set
session.setMaxInactiveInterval() or cookie.setMaxAge()?
We also saw that session.setMaxInactiveInterval() was to prevent from running a lost session indefinitely. JSESSIONID cookie cookie.setMaxAge() won't get us anywhere either.
Use a persistent cookie with the session Id :
I came to this solution after reading the following topics :
How to implement "Stay Logged In" when user login in to the web application by BalusC
http://simple.souther.us/not-so-simple.html by Ben Souther; ben#souther.us
The main idea is to register the user's session in a Map, put into the servlet context. Each time a session is created, it is added to the Map with the JSESSIONID value for key; A persistent cookie is also created to memorize the JSESSIONID value, in order to find the session after the JSESSIONID cookie has been destroyed.
When you close the web browser, JSESSIONID is destroyed. But all the HttpSession objects adress have been kept into a Map on server-side, and you can access the right session with the value saved into the persistent cookie.
First, add two listeners in your web.xml deployment descriptor.
<listener>
<listener-class>
fr.hbonjour.strutsapp.listeners.CustomServletContextListener
</listener-class>
</listener>
<listener>
<listener-class>
fr.hbonjour.strutsapp.listeners.CustomHttpSessionListener
</listener-class>
</listener>
The CustomServletContextListener creates a map at context initialization. This map will register all the sessions created by the user on this application.
/**
* Instanciates a HashMap for holding references to session objects, and
* binds it to context scope.
* Also instanciates the mock database (UserDB) and binds it to
* context scope.
* #author Ben Souther; ben#souther.us
* #since Sun May 8 18:57:10 EDT 2005
*/
public class CustomServletContextListener implements ServletContextListener{
public void contextInitialized(ServletContextEvent event){
ServletContext context = event.getServletContext();
//
// instanciate a map to store references to all the active
// sessions and bind it to context scope.
//
HashMap activeUsers = new HashMap();
context.setAttribute("activeUsers", activeUsers);
}
/**
* Needed for the ServletContextListener interface.
*/
public void contextDestroyed(ServletContextEvent event){
// To overcome the problem with losing the session references
// during server restarts, put code here to serialize the
// activeUsers HashMap. Then put code in the contextInitialized
// method that reads and reloads it if it exists...
}
}
The CustomHttpSessionListener will put the session into the activeUsers map when it is created.
/**
* Listens for session events and adds or removes references to
* to the context scoped HashMap accordingly.
* #author Ben Souther; ben#souther.us
* #since Sun May 8 18:57:10 EDT 2005
*/
public class CustomHttpSessionListener implements HttpSessionListener{
public void init(ServletConfig config){
}
/**
* Adds sessions to the context scoped HashMap when they begin.
*/
public void sessionCreated(HttpSessionEvent event){
HttpSession session = event.getSession();
ServletContext context = session.getServletContext();
HashMap<String, HttpSession> activeUsers = (HashMap<String, HttpSession>) context.getAttribute("activeUsers");
activeUsers.put(session.getId(), session);
context.setAttribute("activeUsers", activeUsers);
}
/**
* Removes sessions from the context scoped HashMap when they expire
* or are invalidated.
*/
public void sessionDestroyed(HttpSessionEvent event){
HttpSession session = event.getSession();
ServletContext context = session.getServletContext();
HashMap<String, HttpSession> activeUsers = (HashMap<String, HttpSession>)context.getAttribute("activeUsers");
activeUsers.remove(session.getId());
}
}
Use a basic form to test a user authentification by name/password. This login.jsp form is meant for test only.
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title><bean:message key="formulaire1Title" /></title>
</head>
<body>
<form action="login.go" method="get">
<input type="text" name="username" />
<input type="password" name="password" />
<input type="submit" />
</form>
</body>
</html>
There we go. This java servlet is forwarding to a login page when the user is not in session, and to another page when he is. It is only meant for testing the persistent session!
public class Servlet2 extends AbstractServlet {
#Override
protected void doGet(HttpServletRequest pRequest,
HttpServletResponse pResponse) throws IOException, ServletException {
String username = (String) pRequest.getParameter("username");
String password = (String) pRequest.getParameter("password");
// Session Object
HttpSession l_session = null;
String l_sessionCookieId = getCookieValue(pRequest, "JSESSIONID");
String l_persistentCookieId = getCookieValue(pRequest, "MY_SESSION_COOKIE");
// If a session cookie has been created
if (l_sessionCookieId != null)
{
// If there isn't already a persistent session cookie
if (l_persistentCookieId == null)
{
addCookie(pResponse, "MY_SESSION_COOKIE", l_sessionCookieId, 1800);
}
}
// If a persistent session cookie has been created
if (l_persistentCookieId != null)
{
HashMap<String, HttpSession> l_activeUsers = (HashMap<String, HttpSession>) pRequest.getServletContext().getAttribute("activeUsers");
// Get the existing session
l_session = l_activeUsers.get(l_persistentCookieId);
}
// Otherwise a session has not been created
if (l_session == null)
{
// Create a new session
l_session = pRequest.getSession();
}
//If the user info is in session, move forward to another page
String forward = "/pages/displayUserInfo.jsp";
//Get the user
User user = (User) l_session.getAttribute("user");
//If there's no user
if (user == null)
{
// Put the user in session
if (username != null && password != null)
{
l_session.setAttribute("user", new User(username, password));
}
// Ask again for proper login
else
{
forward = "/pages/login.jsp";
}
}
//Forward
this.getServletContext().getRequestDispatcher(forward).forward( pRequest, pResponse );
}
The MY_SESSION_COOKIE cookie will save the value of the JSESSIONID cookie. When the JSESSIONID cookie is destroyed, the MY_SESSION_COOKIE is still there with the session ID.
JSESSIONID is gone with the web browser session, but we chose to use a persistent and simple cookie, along with a map of all active sessions put into the application context. The persistent cookie allow us to find the right session in the map.
Don't forget these useful methods made by BalusC to add/get/remove cookies :
/**
*
* #author BalusC
*/
public static String getCookieValue(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
/**
*
* #author BalusC
*/
public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}
/**
*
* #author BalusC
*/
public static void removeCookie(HttpServletResponse response, String name) {
addCookie(response, name, null, 0);
}
}
The last solution was tested with glassfish on localhost, with chrome for webbrowser, on windows. It only depends on a single cookie, and you don't need a database. But actually, I don't know what are the limits of such a mechanism. I only spent the night coming to this solution, without knowing if it will be a good or a bad one.
THANKS
I'm still learning, please tell me if there's any error in my answer. Thanks, #+

The correct answer has many flaws, see my comment there. The matter is actually easier. You will need a persistent datastore (such as a SQL database). You can use ServletContext as well, but the user will be logged out after server restart or application redeploy. Don't forget to properly synchronize, if you use a HashMap in ServletContext, as it might be accessed concurrently from more threads.
Don't hack with server's session and it's ID, it's not under your control and some servers change session ID if a request with JSESSIONID appears after the server expired the original session. Roll your own cookie.
Basically you need:
own cookie, that is not persistent, with a securely random value
a datastore
a javax.servlet.Filter to check login
The filter implementation might look like this:
public class LoginFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// Java 1.8 stream API used here
Cookie loginCookie = Arrays.stream(req.getCookies()).filter(c -> c.getName()
.equals("MY_SESSION_COOKIE")).findAny().orElse(null);
// if we don't have the user already in session, check our cookie MY_SESSION_COOKIE
if (req.getSession().getAttribute("currentUser") == null) {
// if the cookie is not present, add it
if (loginCookie == null) {
loginCookie = new Cookie("MY_SESSION_COOKIE", UUID.randomUUID().toString());
// Store that cookie only for our app. You can store it under "/",
// if you wish to cover all webapps on the server, but the same datastore
// needs to be available for all webapps.
loginCookie.setPath(req.getContextPath());
loginCookie.setMaxAge(DAYS.toSeconds(1)); // valid for one day, choose your value
resp.addCookie(loginCookie);
}
// if we have our cookie, check it
else {
String userId = datastore.getLoggedUserForToken(loginCookie.getValue());
// the datastore returned null, if it does not know the token, or
// if the token is expired
req.getSession().setAttribute("currentUser", userId);
}
}
else {
if (loginCookie != null)
datastore.updateTokenLastActivity(loginCookie.getValue());
}
// if we still don't have the userId, forward to login
if (req.getSession().getAttribute("currentUser") == null)
resp.sendRedirect("login.jsp");
// else return the requested resource
else
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
}
After the user logs in, you should add the value of MY_SEESSION_COOKIE to the datastore along with the userId and remove it upon logout. You must also store the expiration date to the datastore and check it before accepting the token, you must not rely on the browser respecting the maxAge property.
And don't forget to add some datastore cleanup to prevent outstanding cookies to hang around forever.
The above code was not tested in real life, there might be some quirks, but the basic idea should work. It's at least a lot better than the accepted solution.

Related

Servicestack - Passing information between sessions

I have implemented a custom AuthenticateAttribute, AuthUserSession and CredentialsAuthProvider. In the Execute method of my AuthenticateAttribute I do:
public override void Execute(IRequest request, IResponse response, object requestDto)
{
var session = request.GetSession() as IMyCustomAuthUserSession;
// Copy certain request headers into a dictionary on my session object
}
I need to store certain special headers that are sent to me for later use. This works correctly when authentication is not enabled. When authentication IS enabled and the user has to log in, the TryAuthenticate method of my CredentialsAuthProvider class fires:
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var session = authService.GetSession() as IMyCustomAuthUserSession;
}
The sessions in these to methods are not the same since the session I get in the TryAuthenticate method - which fires after the AuthenticateAttribute.Execute method - does not contain the headers I stored there.
The special headers are only sent in the very first call to the web server so I need to get them into the new session of the TryAuthenticate method.
How can I do this?
Passing Session data between logins is going to be difficult as Sessions are invalidated between Authentication attempts. You can choose to retain the same Session Cookies between logins by configuring the AuthFeature plugin with:
Plugins.Add(new AuthFeature(...) {
GenerateNewSessionCookiesOnAuthentication = false
});
Which will retain the same users ss-id/ss-pid cookies on Login.
Use SessionBag for persisting Data between Auth Sessinos
For persisting data outside of an Authenticated User Session you can use a SessionBag, e.g:
//Save
base.SessionBag["cart"] = new Cart { ... };
//Retrieve
var cart = base.SessionBag.Get<Cart>("cart");
Persisting User Data under a Custom Cookie
An alternative solution is to persist data under a Custom Cookie, that way it wont get invalidated by ServiceStack during Authentication.
Where you can register a Global Request Filter to ensure each client/browser has a custom Cookie Id, e.g:
GlobalRequestFilters.Add((req,res,dto) => {
var uniqueId = SessionExtensions.CreateRandomSessionId();
var httpRes = res as IHttpResponse;
httpRes.Cookies.AddPermanentCookie("my-id", uniqueId);
req.Items["my-id"] = uniqueId; //if also needed for this request
});
Then on subsequent requests you can persist data under your unique Cookie Id, e.g:
var uniqueId = req.GetSessionParam("my-id");
var cacheKey = $"urn:Cart:{uniqueId}";
var cache = req.GetCacheClient();
cache.Set(cacheKey, new Cart { ... });
Then later retrieve it with:
var uniqueId = req.GetSessionParam("my-id");
var cacheKey = $"urn:Cart:{uniqueId}";
var cache = req.GetCacheClient();
var cart cache.Get<Cart>(cacheKey);

Invalid XSRF token at /oauth/token

Complete code for a Spring OAuth2 implementation of Multi-Factor Authentication has been uploaded to a file sharing site at this link. Instructions are given below to recreate the current problem on any computer in only a few minutes.
**CURRENT PROBLEM:**
Most of the authentication algorithm works correctly. The program does not break until the very end of the control flow shown below. Specifically, an `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error is being thrown at the end of the **SECOND PASS** below. The app in the link above was developed by adding a custom `OAuth2RequestFactory`, `TwoFactorAuthenticationFilter` and `TwoFactorAuthenticationController` to the `authserver` app of this Spring Boot OAuth2 GitHub sample. **What specific changes need to be made to the code below in order to resolve this CSRF token error and enable 2-factor authentication?**
My research leads me to suspect that the `CustomOAuth2RequestFactory` (API at this link) might be the place to configure a solution because it defines ways for managing `AuthorizationRequest`s and `TokenRequest`s.
**This section of the official OAuth2 spec indicates that the `state` parameter of the request made to the authorization endpoint is the place where the `csrf` token is added.**
Also, the code in the link uses the Authorization Code Grant Type described at this link to the official spec, which would mean that Step C in the flow does not update the `csrf` code, thus triggering the error in Step D. (You can view the entire flow including Step C and Step D in the official spec.)
**CONTROL FLOW SURROUNDING THE CURRENT ERROR:**
The current error is being thrown during the **SECOND PASS** through `TwoFactorAuthenticationFilter` in the flowchart below. Everything works as intended until the control flow gets into the **SECOND PASS**.
The following flowchart illustrates the control flow of the two factor authentication process that is employed by the code in the downloadable app.
Specifically, the Firefox `HTTP` Headers for the sequence of `POST`s and `GET`s show that the same `XSRF` cookie is sent with every request in the sequence. The `XSRF` token values do not cause a problem until after the `POST /secure/two_factor_authentication`, which triggers server processing at the `/oauth/authorize` and `/oauth/token` endpoints, with `/oauth/token` throwing the `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error.
To understand the relationship between the above control flow chart and the `/oauth/authorize` and `/oauth/token` endpoints, you can compare the above flowchart side by side with the chart for the single factor flow at the official spec in a separate browser window. The **SECOND PASS** above simply runs through the steps from the one-factor official spec a second time, but with greater permissions during the **SECOND PASS**.
**WHAT THE LOGS SAY:**
The HTTP Request and Response Headers indicate that:
1.) A POST to `9999/login` with the correct `username` and `password` submitted results in a redirect to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` followed by a `GET 9999/secure/two_factor_authenticated`. One XSRF token remains constant across these exchanges.
2.) A POST to `9999/secure/two_factor_authentication` with the correct pin code sends the same `XSRF` token, and gets successfully re-directed to `POST 9999/oauth/authorize` and makes it into `TwoFactorAuthenticationFilter.doFilterInternal()` and proceeds to `request 9999/oauth/token`, but `9999/oauth/token` rejects the request because the same old XSRF token does not match a new `XSRF` token value, which was apparently created during the **FIRST PASS**.
One obvious difference between `1.)` and `2.)` is that the second `request 9999/oauth/authorize` in `2.)` does not contain the url parameters which are included in the first request to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` in `1.)`, and also defined in the official spec. But it is not clear if this is causing the problem.
Also, it is not clear how to access the parameters to send a fully formed request from the `TwoFactorAuthenticationController.POST`. I did a SYSO of the `parameters` `Map` in the `HttpServletRequest` for the `POST 9999/secure/two_factor_authentication` controller method, and all it contains are the `pinVal` and `_csrf` variables.
You can read all the HTTP Headers and Spring Boot logs at a file sharing site by clicking on this link.
**A FAILED APPROACH:**
I tried #RobWinch's approach to a similar problem in the Spring Security 3.2 environment, but the approach does not seem to apply to the context of Spring OAuth2. Specifically, when the following `XSRF` update code block is uncommented in the `TwoFactorAuthenticationFilter` code shown below, the downstream request headers do show a different/new `XSRF` token value, but the same error is thrown.
if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
response.setHeader("XSRF-TOKEN"/*"X-CSRF-TOKEN"*/, token.getToken());
}
**This indicates that the `XSRF` configuration needs to be updated in a way that `/oauth/authorize` and `/oauth/token` are able to talk with each other and with the client and resource apps to successfully manage the `XSRF` token values.** Perhaps the `CustomOAuth2RequestFactory` is what needs to be changed to accomplish this. But how?
**RELEVANT CODE:**
The code for `CustomOAuth2RequestFactory` is:
public class CustomOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
public static final String SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME = "savedAuthorizationRequest";
public CustomOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
super(clientDetailsService);
}
#Override
public AuthorizationRequest createAuthorizationRequest(Map authorizationParameters) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession(false);
if (session != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
if (authorizationRequest != null) {
session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
return authorizationRequest;
}
}
return super.createAuthorizationRequest(authorizationParameters);
}
}
The code for `TwoFactorAuthenticationFilter` is:
//This class is added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2
/**
* Stores the oauth authorizationRequest in the session so that it can
* later be picked by the {#link com.example.CustomOAuth2RequestFactory}
* to continue with the authoriztion flow.
*/
public class TwoFactorAuthenticationFilter extends OncePerRequestFilter {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private OAuth2RequestFactory oAuth2RequestFactory;
//These next two are added as a test to avoid the compilation errors that happened when they were not defined.
public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED";
public static final String ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED = "ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED";
#Autowired
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
}
private boolean twoFactorAuthenticationEnabled(Collection authorities) {
System.out.println(">>>>>>>>>>> List of authorities includes: ");
for (GrantedAuthority authority : authorities) {
System.out.println("auth: "+authority.getAuthority() );
}
return authorities.stream().anyMatch(
authority -> ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED.equals(authority.getAuthority())
);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("------------------ INSIDE TwoFactorAuthenticationFilter.doFilterInternal() ------------------------");
// Check if the user hasn't done the two factor authentication.
if (AuthenticationUtil.isAuthenticated() && !AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
System.out.println("++++++++++++++++++++++++ AUTHENTICATED BUT NOT TWO FACTOR +++++++++++++++++++++++++");
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
/* Check if the client's authorities (authorizationRequest.getAuthorities()) or the user's ones
require two factor authenticatoin. */
System.out.println("======================== twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) is: " + twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) );
System.out.println("======================== twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) is: " + twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) );
if (twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) ||
twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities())) {
// Save the authorizationRequest in the session. This allows the CustomOAuth2RequestFactory
// to return this saved request to the AuthenticationEndpoint after the user successfully
// did the two factor authentication.
request.getSession().setAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME, authorizationRequest);
// redirect the the page where the user needs to enter the two factor authentiation code
redirectStrategy.sendRedirect(request, response,
ServletUriComponentsBuilder.fromCurrentContextPath()
.path(TwoFactorAuthenticationController.PATH)
.toUriString());
return;
}
}
//THE NEXT "IF" BLOCK DOES NOT RESOLVE THE ERROR WHEN UNCOMMENTED
//if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
// CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
// this is the value of the token to be included as either a header or an HTTP parameter
// response.setHeader("XSRF-TOKEN", token.getToken());
//}
filterChain.doFilter(request, response);
}
private Map paramsFromRequest(HttpServletRequest request) {
Map params = new HashMap();
for (Entry entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), entry.getValue()[0]);
}
return params;
}
}
**RE-CREATING THE PROBLEM ON YOUR COMPUTER:**
You can recreate the problem on any computer in only a few minutes by following these simple steps:
1.) Download the zipped version of the app from a file sharing site by clicking on this link.
2.) Unzip the app by typing: `tar -zxvf oauth2.tar(2).gz`
3.) launch the `authserver` app by navigating to `oauth2/authserver` and then typing `mvn spring-boot:run`.
4.) launch the `resource` app by navigating to `oauth2/resource` and then typing `mvn spring-boot:run`
5.) launch the `ui` app by navigating to `oauth2/ui` and then typing `mvn spring-boot:run`
6.) Open a web browser and navigate to `http : // localhost : 8080`
7.) Click `Login` and then enter `Frodo` as the user and `MyRing` as the password, and click to submit.
8.) Enter `5309` as the `Pin Code` and click submit. **This will trigger the error shown above.**
You can view the complete source code by:
a.) importing the maven projects into your IDE, or by
b.) navigating within the unzipped directories and opening with a text editor.
You can read all the HTTP Headers and Spring Boot logs at a file sharing site by clicking on this link.
One idea that popped to my head:
If session fixation is activated, a new session is created after the user authenticated successfully (see SessionFixationProtectionStrategy). This will also of course create a new csrf token if you use the default HttpSessionCsrfTokenRepository. Since you're mentioning the XSRF-TOKEN header I assume you use some JavaScript frontend. I could imagine that the original csrf token that was used for the login is stored and reused afterwards - which would not work because this csrf token is not valid anymore.
You may try disabling session fixation (http.sessionManagement().sessionFixation().none() or <session-management session-fixation-protection="none"/>) or re-get the current CSRF token after login.
Your CustomOAuth2RequestFactory is putting the previous request in-place of the current request. However, you are not updating the XSRF token in the old request when you make this switch. Here is what I would suggest for the updated CustomOAuth2Request:
#Override
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession(false);
if (session != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
if (authorizationRequest != null) {
session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
//UPDATE THE STATE VARIABLE WITH THE NEW TOKEN. THIS PART IS NEW
CsrfToken csrf = (CsrfToken) attr.getRequest().getAttribute(CsrfToken.class.getName());
String attrToken = csrf.getToken();
authorizationRequest.setState(attrToken);
return authorizationRequest;
}
}
return super.createAuthorizationRequest(authorizationParameters);
}
I am revisiting this because my initial answer draft got downvoted. This version is further along the same path, which I believe is the right avenue of approach.

Display message to user on expired session when using wicket-auth-roles

Hi I have been unable to solve the following problem in Wicket 6.*:
In our webapp we are using wicket-auth-roles to manage authentication/authorization. When session expires, user should be redirected to a page set by getApplicationSettings().setPageExpiredErrorPage(SomePage.class) on his next action. However, if the user tries to access a page which doesn't allow guests, he is redirected to a login page skipping the PageExpiredPage altogether.
My question is - how can I display "Session has expired." message to the user?
Among other things, I have tried session.info("message") during onInvalidate phase of session's lifecycle, however the feedback message is then rendered on the first page after login (not on the login page).
Thank you for your anwsers.
You could use a RequestCycleListener to record when a PageExpiredException is thrown.
public class ExceptionMapperListener extends AbstractRequestCycleListener {
#Override
public IRequestHandler onException(RequestCycle cycle, Exception ex) {
if (ex instanceof PageExpiredException) {
// Record in session or request cycle
// OR
// Create a RenderPageRequestHandler yourself and add a page parameter
// See DefaultExceptionMapper#internalMap(Exception)
}
return null;
}
}
// In Application#init():
getRequestCycleListeners().add(new ExceptionMapperListener());
ORINAL ANSWER
(kept because it could still help...)
I haven't tried it myself since I don't use wicket-auth-roles, but try overriding the method AuthenticatedWebApplication#restartResponseAtSignInPage() with something like this:
if (isSessionExpired()) {
PageParameters params = new PageParameters();
params.add("showSessionExpired", true);
throw new RestartResponseAtInterceptPageException(getSignInPageClass(), params);
} else {
throw new RestartResponseAtInterceptPageException(getSignInPageClass());
}
And then in the SignInPageClass, display the desired message if the showSessionExpired page parameter is present.
I'm not sure how you implement isSessionExpired(), but you seem to have that part already covered.
OR
Depending on how you implemented isSessionExpired(), maybe you could do the following in your SignInPageClass:
if (sessionExpired()) {
session.info("message")
}
After bernie put me on the right path, I eventually figured out a solution to the problem:
First it is required to override RequestCycleListener:
public class SessionExpiredListener extends AbstractRequestCycleListener {
public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler) {
if (handler instanceof IPageRequestHandler) {
IPageRequestHandler pageHandler = (IPageRequestHandler) handler;
HttpServletRequest request = (HttpServletRequest) cycle.getRequest().getContainerRequest();
//check whether the requested session has expired
boolean expired = request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid();
//check whether the requested page can be instantiated with the current session
boolean authorized = Session.get().getAuthorizationStrategy().isInstantiationAuthorized(pageHandler.getPageClass());
if (expired && !authorized) {
throw new PageExpiredException("Session has expired!");
}
}
super.onRequestHandlerResolved(cycle, handler);
}
}
Check for authorized prevents the session-expired message from displaying on log-out or when accessing unprotected pages.
Finally, you must register your listener and PageRequestHandlerTracker in your WebApplication:
getRequestCycleListeners().add(new SessionExpiredListener());
getRequestCycleListeners().add(new PageRequestHandlerTracker());

ASP.NET MVC3 - Anti-CSRF and Session timeout

I am implementing Anti-Forgery framework as described here:
http://weblogs.asp.net/srkirkland/archive/2010/04/14/guarding-against-csrf-attacks-in-asp-net-mvc2.aspx
Plus, to minimize the coding effort, I did the token insertion part at client side handling form.onsumit and ajaxsend events. Everything works fine – until the session expires.
In my application, I display a popup when the user session gets timed out where the user can re-login and continue without refreshing the current page so that the work-in-progress will be safe. But this doesn't go well with the Anti-CSRF logic. When the user tries to re-login after a timed-out session, this throws a CSRF exception as the cookie (__RequestVerificationToken_Lw__) is already expired and all the future POSTs will be invalid until next page refresh.
Is there any way to set the cookie end time to a future date rather than 'session'? I tried to edit Response.Cookie but that made the cookie invalid.
Any help would be appreciated.
Thanks
At the time of user session out (when displaying a popup) is it possible for you to set the httpcookie with expiry in server side.
I have extracted some code from the microsofts antiforgery token implementation.
internal static string GetAntiForgeryTokenName(string appPath)
{
if (string.IsNullOrEmpty(appPath))
{
return "__RequestVerificationToken";
}
return "__RequestVerificationToken_" + Base64EncodeForCookieName(appPath);
}
private static string Base64EncodeForCookieName(string s)
{
byte[] bytes = Encoding.UTF8.GetBytes(s);
string text = Convert.ToBase64String(bytes);
return text.Replace('+', '.').Replace('/', '-').Replace('=', '_');
}
Below code which set the cookie in server side.
string antiForgeryTokenName = GetAntiForgeryTokenName(HttpContext.Request.ApplicationPath);
HttpCookie httpCookie = HttpContext.Request.Cookies[antiForgeryTokenName];
HttpCookie httpCookie2 = new HttpCookie(antiForgeryTokenName, httpCookie.Value)
{
HttpOnly = true
//// your domain Domain = ,
//// path Path = ,
//// set path Expires =
};
HttpContext.Response.Cookies.Set(httpCookie2);
Please note that I haven't tested this code, just give a try if you dont have any other options.

JSF 2.0: cookies are not persisted immediately after AJAX call, HTTP request required

Ajax call performed in order to remove item from shopping cart - removeOrder() method is called
UI removeOrder() call(JSF&Primefaces):
<p:commandButton value="clean" actionListener="#{showProducts.removeOrder}"
process="#form" update="#form,:ccId:cCart:ccSizeId,:ccId:cCart:ccTotId" immediate="true">
<f:attribute name="remove" value="#{cart.name}"/>
</p:commandButton>
Backend removeOrder() call(managed bean)
public void removeOrder(ActionEvent e) {
String productName = (String) e.getComponent().getAttributes().get("remove");
Product p = getProductByName(productName);
inCart.remove(p);
persistCookies();
emptyCartNotifier();
totalRendered();
}
Here cookies is persisted,output of this method as is expected,Cookie array contains cookies with empty values,that's OK:
private void persistCookies() {
HttpServletResponse httpServletResponse = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
String ids = "";
for (Product prod : inCart) {
// TODO change logic to support real count,for now 1 is available only
ids += prod.getId() + ";" + prod.getCount() + "_";
}
Cookie cookie = new Cookie(SC_COOKIE, ids);
Cookie cookie2 = new Cookie(SC_SIZE, String.valueOf(inCart.size()));
Cookie cookie3 = new Cookie(SC_TOTAL_PRICE, String.valueOf(subTotal));
cookie3.setPath("/");
cookie3.setMaxAge(TWO_WEEKS);
httpServletResponse.addCookie(cookie3);
cookie.setPath("/");
cookie.setMaxAge(TWO_WEEKS);
cookie2.setPath("/");
cookie2.setMaxAge(TWO_WEEKS);
httpServletResponse.addCookie(cookie);
httpServletResponse.addCookie(cookie2);
}
Here problem occurred, the method emptyCartNotifier() see non-empty "previous" Cookies array
private String emptyCartNotifier() {
HttpServletRequest httpServletRequest = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
Cookie[] cookies = httpServletRequest.getCookies();
boolean isCookiePresent = false;
if (cookies != null) {
for (Cookie c : cookies) {
if (SC_COOKIE.equals(c.getName()) && (!c.getValue().isEmpty())) {
isCookiePresent = true;
}
}
}
if (inCart.isEmpty() && (!isCookiePresent)) {
emptyCartNotifier = "you cart is empty";
isFormRendered = false;
} else {
emptyCartNotifier = "";
isFormRendered = true;
}
return emptyCartNotifier;
}
After any HTTP request performed, that Cookie array is really cleaned up.
As I see , clash is:after AJAX call cleans cookie, that HttpServletRequest contains non-empty cookie until new HTTP request performed(user submit button or go by link).
Is there solution or good practice for immediate cookie management,when web-app combines AJAX and non-AJAX calls?
Thank you.
Your cookie problem is one thing, but I think there is a much easier way to get things done.
In your page, just replace the pages you mention with this:
<p:commandButton value="clean" action="#{showProducts.removeOrder(cart)}"
process="#form" update="#form,:ccId:cCart:ccSizeId,:ccId:cCart:ccTotId" immediate="true" />
Backing bean (e.g. session scoped):
private double subtotal; // don't forget getter and setter
public void removeOrder(Product product) {
inCart.remove(product);
// calculate subtotal
}
I guess you show the subtotal and maybe list all products after calling removeOrder. Just recalculate subtotal after removing the product, and make sure the shopping cart is refreshed (update attribute).
This code is simple enough and easy to understand, and yet does everything you need. No need to "manually" manage cookies.
Wait, what? In emptyCartNotifier, You are calling getCookies on the HttpRequest object, which is supposed to contain the cookies that were in the HTTP request that triggered your method, so of course you don't see changes until the next request.
You could try setting your cookies setHttpOnly(true), but the question is why would you need "ajax-scoped" cookie persistence at all?
Why not use local variables in your view/request/session scoped beans? They're actually designed for that sort of tasks. If you want additional cookie persistence you do it in corresponding setters or action methods.

Resources