An SSL Termination Issue in Stormpath
April 13, 2016
Our application sits behind Amazon’s Elastic Load Balancer (ELB), which is a convenient place for terminating SSL connections. We use the Stormpath servlet plugin and its token authentication feature for our Stormpath integration. For some reason though, requests from users trying to authenticate with our application errored out with the following message:
[http-listener-1(3)] DEBUG c.s.s.s.mvc.AccessTokenController - OAuth Access Token request failed.
com.stormpath.sdk.servlet.filter.oauth.OauthException: A secure HTTPS connection is required for token requests - this is a requirement of the OAuth 2 specification.
A look at the request headers from clients showed that X-Forwarded-Proto
headers were getting set. The X-Forwarded-Proto
header is responsible for informing the server of the protocol at the origin where a request was initiated. In our case, we wanted Glassfish to treat connections that had X-Forwarded-Proto
header values of https
as secured, so we configured it to do so. That however, still didn’t solve the problem.
After some time spent in frustration and consideration of Stormpath alternatives, I found this Github issue, which led to the following workaround:
Create this Factory:
public class SecureResolverFactory extends ConfigSingletonFactory<Resolver<Boolean>> { public static final String LOCALHOST_RESOLVER = "stormpath.web.localhost.resolver"; @Override protected Resolver<Boolean> createInstance(ServletContext servletContext) throws Exception { return new SecureForwardedProtoAwareResolver(new IsHTTPSForwardedProtoResolver(), new SecureRequiredExceptForLocalhostResolver(getConfig().getInstance(LOCALHOST_RESOLVER))); } }
In stormpath.properties add:
stormpath.web.accessToken.authorizer.secure.resolver = com.mycompany.resolver.SecureResolverFactory
Create these Resolvers:
public class IsHTTPSForwardedProtoResolver implements Resolver<Boolean> { private static final String HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"; @Override public Boolean get(HttpServletRequest request, HttpServletResponse response) { String protocol = request.getHeader(HEADER_FORWARDED_PROTO); if (protocol != null && protocol.equalsIgnoreCase("https")) { return true; } return false; } } public class SecureForwardedProtoAwareResolver implements Resolver<Boolean> { private final Resolver<Boolean> isHTTPSForwardedProtoResolver; private final Resolver<Boolean> secureRequiredExceptForLocalhostResolver; public SecureForwardedProtoAwareResolver(Resolver<Boolean> isHTTPSForwardedProtoResolver, Resolver<Boolean> secureRequiredExceptForLocalhostResolver) { Assert.notNull(isHTTPSForwardedProtoResolver, "isHTTPSForwardedProtoResolver resolver cannot be null."); Assert.notNull(secureRequiredExceptForLocalhostResolver, "secureRequiredExceptForLocalhost resolver cannot be null."); this.isHTTPSForwardedProtoResolver = isHTTPSForwardedProtoResolver; this.secureRequiredExceptForLocalhostResolver = secureRequiredExceptForLocalhostResolver; } @Override public Boolean get(HttpServletRequest request, HttpServletResponse response) { if (this.isHTTPSForwardedProtoResolver.get(request, response)) { return false; } return this.secureRequiredExceptForLocalhostResolver.get(request, response); } }
That’s it, Stormpath now considers requests with
X-Forwarded-Proto
values ofhttps
as secured.