Introduction

Following on from CSRF Mitigation Options, here we explore how the CsrfFlowExecutionListener[1] CSRF mitigation technique can be applied to the IdP's external authentication mechanism, helping to prevent Login CSRF across ‘external’ authentication strategies.

There are of course any number of possible custom external authentication strategies, not all will benefit from or require CSRF mitigation. Broadly speaking, these can be split into four types:

For (1) and (2), as there is no interaction with the user which could be forged by an attacker, simply passing a CSRF token in and out of the server-side authentication strategy provides no security benefit.

For (3), the developer must embed a CSRF token into any HTML forms that are written back to the client. Once POSTed back to the Servlet, the developer must (within that request/response cycle) call finishExternalAuthentication on the ExternalAuthentication API. The ExternalAuthenticationImpl then needs to guarantee the token is present in the redirect back to the IdP.

For (4), A CSRF token is redundant for any interaction with the user that is not the final interaction before the token is sent back to the IdP to be checked. That is unless the developer of the authentication strategy takes responsibility for checking the token during each request.

In implementation, consideration must also be given to situations where CSRF protection has been disabled, and how best to communicate that to the developer.

Implementation

If CSRF protection is enabled, and external view states are included by default, any external authentication servlet must adhere to the following process:


Getting the CsrfToken from the viewScope.

Option 1

Extend ExternalAuthenticationImpl to take a CsrfToken in its constructor (or as a parameter), and inject it during the sub context creation in the view-state (on-render).

 <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(calledAsExtendedFlow\?:false,viewScope.csrfToken)), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />

Set the CsrfToken as an attribute of the HTTP request when starting authentication e.g. in ExternalAuthenticationImpl#doStart.

request.setAttribute(CSRF_TOKEN_PARAM, csrfToken);

The developer of an external authentication mechanism must then extract the token from the HTTP request, e.g.

final CsrfToken tokenFromRequest =  (CsrfToken)request.getAttribute(ExternalAuthentication.CSRF_TOKEN_PARAM);

Notes:


Option 2

Extend the ExternalAutthentication API to extract the CsrfToken from the spring-webflow active session viewScope (using the new external auth API mechanism):

@Nullable public static CsrfToken getCsrfToken(@Nonnull @NotEmpty final String key,
            @Nonnull final HttpServletRequest request) throws ExternalAuthenticationException {

        final Object obj = request.getServletContext().getAttribute(SWF_KEY);
        if (!(obj instanceof FlowExecutorImpl)) {
            throw new ExternalAuthenticationException("No FlowExecutor available in servlet context");
        }

        try {
            final FlowExecutionRepository repo = ((FlowExecutorImpl) obj).getExecutionRepository();
            ExternalContextHolder.setExternalContext(
                    new ServletExternalContext(request.getServletContext(), request, null));
            
            final FlowExecution execution = repo.getFlowExecution(repo.parseFlowExecutionKey(key));
            final MutableAttributeMap<Object> viewScopeMap = execution.getActiveSession().getViewScope();
            final Object tokenObject = viewScopeMap.get("csrfToken");
            //check csrf is enabled\?
            if (tokenObject==null || !(tokenObject instanceof CsrfToken)) {
                return null;
            }
            return (CsrfToken)tokenObject;
            
          
        } catch (final FlowExecutionRepositoryException | IllegalStateException e) {
            throw new ExternalAuthenticationException("Error retrieving flow conversation or view scope", e);
        } finally {
            ExternalContextHolder.setExternalContext(null);
        }
    }

The developer of an external authentication mechanism must then retrieve the token from the ExternalAuthentication API, e.g.

final String key = ExternalAuthentication.startExternalAuthentication(request);
final CsrfToken token =  ExternalAuthentication.getCsrfToken(key, request);

Embedding the CSRF Token in a suitable HTML response

To work as protection for a CSRF attack, the CSRF token parameter name and value must be embedded in HTTP forms sent in a response to the user-agent. For example, as a simple example in JSP:

<%@ page pageEncoding="UTF-8" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ page import="net.shibboleth.idp.authn.ExternalAuthentication" %>
<%@ page import="net.shibboleth.idp.authn.ExternalAuthenticationException" %>
<%@ page import="net.shibboleth.utilities.java.support.net.CsrfToken" %>
<%
CsrfToken csrfToken = null;
String redirect = null;
  try {
      final String key = ExternalAuthentication.startExternalAuthentication(request);
      csrfToken = ExternalAuthentication.getCsrfToken(key,request);
      redirect = ExternalAuthentication.getExternalRedirect("/idp/external.jsp",key);
 
  } catch (final ExternalAuthenticationException e) {
      throw new ServletException("Error processing external authentication request", e);
  }
%>
<form action="<%=redirect%>" method="post">
		<input name="username" id="username">
		 <input type="hidden" name="<%=csrfToken.getParameterName()%>" value="<%=csrfToken.getToken()%>" id="csrf_token">
		<input type="submit" value="Login">
</form>

Passing the token back to the IdP

External authentication servlets are ‘redirected’ back into the IdP external-auth webflow. The token is required in the GET request, and hence must be added as a query parameter to the URL e.g. as a very simple example, inside the ExternalAuthenticationImpl#doFinish method:

response.sendRedirect(extContext.getFlowExecutionUrl()+"&csrf_token="+request.getParameter("csrf_token"));


---

[1] The CsrfFlowExecutionListener provides a core cross-cutting security concern across IdP view states by injecting and validation CSRF tokens.