Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for OPTIONS requests to be passed through auth filters #1244

Merged

Conversation

jdonenine
Copy link
Contributor

@jdonenine jdonenine commented Nov 18, 2022

Fixes #1245

  • Browsers do not pass authorization content in OPTIONS requests
  • This causes all preflight checks in the browser to fail when making a request to an authenticated endpoint when doing so with CORS enabled
  • The change makes it possible to utilize a JWT from within the browser via fetch with CORS -- allowing for the separation of the frontend from the backend
  • This also modifies the Shiro configuration such that it properly secures the cookies that it generates for CORS usage.

I think this behavior should probably be considered a bug, so I tied the behavior to the already in-use environment variable REAPER_ENABLE_CROSS_ORIGIN which when set to true will cause all OPTIONS requests to be allowed without an auth check.

* Browsers do not (by spec) pass authorization content in OPTIONS requests
* This causes all preflight checks in the browser to fail when making a request to an authenticated endpoint when doing so with CORS enabled
* The change makes it possible to utilize a JWT from within the browser via fetch with CORS -- allowing for the separation of the frontend from the backend
@jdonenine jdonenine marked this pull request as ready for review November 19, 2022 02:27
@jdonenine jdonenine changed the title Allow for OPTION requests to be passed through auth filters Allow for OPTIONS requests to be passed through auth filters Nov 19, 2022
…vironment variable to trigger the allow of all options requests
@jdonenine
Copy link
Contributor Author

So, it turns out that this still isn't sufficient to get through the CSRF protections in browsers because of the way the cookies that Shiro creates are created. I'm still working on that aspect of things, I think it's just a shiro configuration to be made, but it's elusive to say the least. Going to make this a draft for the moment while I sort that out.

@jdonenine jdonenine marked this pull request as draft November 23, 2022 12:49
@jdonenine jdonenine marked this pull request as ready for review November 23, 2022 15:45
Comment on lines 26 to 52
public final class RequestUtils {
public static final String ENABLE_CORS_ENV_VAR_NAME = "REAPER_ENABLE_CROSS_ORIGIN";

private RequestUtils() {}

public static boolean isOptionsRequest(ServletRequest request) {
if (request != null && request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().equalsIgnoreCase(HttpMethod.OPTIONS)) {
return true;
}
}
return false;
}

@VisibleForTesting
static boolean isCorsEnabled(String corsEnabledEnvVarValue) {
if (corsEnabledEnvVarValue != null) {
return Boolean.parseBoolean(corsEnabledEnvVarValue.trim().toLowerCase());
}
return false;
}

public static boolean isCorsEnabled() {
String corsEnabledEnvVarValue = System.getenv(ENABLE_CORS_ENV_VAR_NAME);
return isCorsEnabled(corsEnabledEnvVarValue);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reviewing this and thinking about another requirement we have (which is to shut down the sessions despite being active after the configured timeout), I'm of the opinion that we shouldn't rely on env variable.
We can statically set a isCorsEnabled variable in RequestUtils which will be initialized in the ReaperApplication.java initialization phase, and feed on the corresponding config setting: config.isEnableCrossOrigin()

Suggested change
public final class RequestUtils {
public static final String ENABLE_CORS_ENV_VAR_NAME = "REAPER_ENABLE_CROSS_ORIGIN";
private RequestUtils() {}
public static boolean isOptionsRequest(ServletRequest request) {
if (request != null && request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().equalsIgnoreCase(HttpMethod.OPTIONS)) {
return true;
}
}
return false;
}
@VisibleForTesting
static boolean isCorsEnabled(String corsEnabledEnvVarValue) {
if (corsEnabledEnvVarValue != null) {
return Boolean.parseBoolean(corsEnabledEnvVarValue.trim().toLowerCase());
}
return false;
}
public static boolean isCorsEnabled() {
String corsEnabledEnvVarValue = System.getenv(ENABLE_CORS_ENV_VAR_NAME);
return isCorsEnabled(corsEnabledEnvVarValue);
}
}
public final class RequestUtils {
private static boolean isCorsEnabled = false;
private RequestUtils() {}
public static void setCorsEnabled(boolean enabled) {
isCorsEnabled = enabled;
}
public static boolean isCorsEnabled() {
return isCorsEnabled;
}
public static boolean isOptionsRequest(ServletRequest request) {
if (request != null && request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().equalsIgnoreCase(HttpMethod.OPTIONS)) {
return true;
}
}
return false;
}
}

Initialization can be done as follows in ReaperApplication.java:

 if (config.isAccessControlEnabled()) {
      RequestUtils.setCorsEnabled(config.isEnableCrossOrigin());
      SessionHandler sessionHandler = new SessionHandler();
      sessionHandler.setMaxInactiveInterval((int) config.getAccessControl().getSessionTimeout().getSeconds());
      environment.getApplicationContext().setSessionHandler(sessionHandler);
      environment.servlets().setSessionHandler(sessionHandler);
      environment.jersey().register(new ShiroExceptionMapper());
      environment.jersey().register(new LoginResource());
      environment.jersey().register(new ShiroJwtProvider(context));
    }

wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so the latest commit switches to initializing with config property instead of directly using the env variable... duh, I should have seen that to start 😔 so thanks for that suggestion!

But, I wasn't sure why you suggested tangling up the enabling of CORS within the isAccessControlEnabled() block as you mention here?

I initialized things in place with the other CORS related configuration that already happened within ReaperApplication.java's startup code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, I wasn't sure why you suggested tangling up the enabling of CORS within the isAccessControlEnabled() block as you mention here?

I was under the impression that we needed it to allow OPTIONS calls when auth is enabled. Isn't it what you were trying to do here?
Either way, I'm fine with initializing it outside of that block 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, the two can be unrelated.

@jdonenine jdonenine requested a review from adejanovski December 8, 2022 12:37
Copy link
Contributor

@adejanovski adejanovski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome sauce DiNoto 🥳
Thanks!

@adejanovski adejanovski merged commit 74c2703 into thelastpickle:master Dec 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow for OPTIONS requests to be passed through auth filters
2 participants