Expected Behavior

The developer can override the redirect strategy in configuration to for OAuth2AuthorizationCodeGrantFilter to issue request forward into the existing web context (i.e., a relative path) at the end of authentication.

Current Behavior

Current behavior enables only redirects. The redirect strategy in OAuth2AuthorizationCodeGrantFilter cannot be overridden by configuration.

The active request is the authorization server's callback after a successful grant. Within the context of that request, this filter will send a request to the token URI to obtain an access token. After completion, the filter responds with a redirect to the authorization server. The target of the redirect is the original incoming request that triggered the authentication.

Context

How has this issue affected you? I have a client whose custom authorization server does not respond to this last redirect. My application doesn't get the callback to the business level request.

What are you trying to accomplish?

My client is a SmartOnFhir implementation. I'm implementing a 'launch' use case. To initiate a launch, the SmartOnFhir implementation sends an opaque token as a query parameter to an endpoint in my application. I'm using the oauth2client configuration. In my controller, I call OAuth2AuthorizedClientProvider.authenticate to start the authentication flow. SmartOnFhir will send confidential data within the access token after successful authentication. My business logic has to obtain and decode the access token. This is why I require a callup to my controller. If the redirect worked, things would work.

What other alternatives have you considered? I'm using the oauth2client configuration. Maybe I should try the oauth2login configuration. Using oauth2login, I believe I can easily use the ForwardAuthenticationSuccessHandler without a problem. I'm not wild about this approach: I'm not that interested in user data in my application, and I have to figure out how to disable the login page (if necessary) because there is really only one authorization provider by the requirement of SmartOnFhir.

Are you aware of any workarounds? No, only to copy the OAuth2AuthorizationCodeGrantFilter into my project, make a change within it to support forward, and add it to the security filter change before the existing original filter.

Comment From: sjohnr

Hi @jjfraney-cg, thanks for reaching out and welcome to the project!

I think it would be plausible to add an AuthenticationSuccessHandler to this filter to enable customization, if a redirect truly doesn't make sense in certain cases. Before we discuss that however, I am curious about your use case if you wouldn't mind going into a bit more detail. In particular:

After completion, the filter responds with a redirect to the authorization server. The target of the redirect is the original incoming request that triggered the authentication.

I wouldn't expect the "redirect to the authorization server" to be happening in this context as OAuth2AuthorizationCodeGrantFilter is processing the response from the authorization server. The next sentence seems to state what I would expect to happen, so could you perhaps describe the steps of the flow to clarify?

I call OAuth2AuthorizedClientProvider.authenticate to start the authentication flow

Are you saying that you trigger the authorization_code flow from the client by directly calling the provider? Does that mean you are not using the OAuth2AuthorizationRequestRedirectFilter?

Comment From: jjfraney-cg

Thanks for quick response!

After completion, the filter responds with a redirect to the authorization server. The target of the redirect is the original incoming request that triggered the authentication.

I wouldn't expect the "redirect to the authorization server" to be happening in this context as OAuth2AuthorizationCodeGrantFilter is processing the response from the authorization server.

Right. It's not a redirect to the authorization server. That language is incorrect. I'm sorry for being sloppy.

I call OAuth2AuthorizedClientProvider.authenticate to start the authentication flow

Are you saying that you trigger the authorization_code flow from the client by directly calling the provider? Does that mean you are not using the OAuth2AuthorizationRequestRedirectFilter?

I am using the OAuth2AuthorizationRequestRedirectFIlter. It handles a ClientAuthorizationRequiredException from my controller. My controller delegates the decision to throw that exception to OAuth2AuthorizedClient.authenticate. I am learning and reconsidering that design. I would prefer not to throw ClientAuthorizationRequiredException directly because I sense it is an internal implementation artifact of Spring Security.

Maybe your question is: "Why not use webclient to trigger the authorization_code flow?". In case that's your question: Because of the nature of the SmartOnFhir specification. An http request to the resource server doesn't make sense until after the token is in hand.

Comment From: sjohnr

Sadly, I'm not familiar with those specifications. However, I did glance at app-launch which seems to match what you're describing. The opaque token you're referring to is the launch parameter, correct?

SmartOnFhir will send confidential data within the access token after successful authentication. My business logic has to obtain and decode the access token. This is why I require a callup to my controller. If the redirect worked, things would work.

Once the authorization_code flow is completed back on the client, you will have access to the token without needing to decode it yourself. So I don't think you need to use a forward to accomplish this.

For example, assuming you have a client registration called fhir-client:

@GetMapping(value = "/app/launch")
public SomeResource getResourceFromFhirServer(
        @RegisteredOAuth2AuthorizedClient("fhir-client")
                OAuth2AuthorizedClient authorizedClient) {
    // Use the token from authorizedClient and/or make request to backend...
}

If there's no access token yet, you'll be directed through the authorization_code flow automatically. You'll be redirected back to /app/launch once the client is authorized.

An http request to the resource server doesn't make sense until after the token is in hand.

Would the above code accomplish this, or am I still missing a piece of the puzzle?

Comment From: jjfraney-cg

You're not missing any piece of this puzzle. You found the fhir spec.

That annotation rings the bell.

Thanks.

Comment From: jjfraney-cg

Assume different end users are driving a unique app-launch request. Shouldn't each have an opportunity to grant authorization? It appears that after the first session drives an authorization grant, the following sessions reuse the token. Am I expecting wrongly?

Comment From: sjohnr

@jjfraney-cg, it's hard to say without a bit more context. Do you mean that you log out of the client application, and log in with a different user, but when you go through the authorization code flow, you get a new token without consent? If so, that's typically because the authorization server still has a session available as whatever user first logged in and provided consent. Or are you speaking of something else?

Comment From: jjfraney-cg

I'm watching the servlet session JSESSIONID and the token. The JSESSIONID is different per call, but the token is the same. So, either I have the notion of a session wrong, or I have to convince Spring to create a new token per session.

My app doesn't have user context. All anonymous. No login/logout.

I think oauth2login may be better than oauth2client configuration. Would oauth2login provide a new token after consent per login?

oauth2login configuration gives me these challenges: - I don't know how to disable the automated login screen that comes with oauth2login. I don't need it as there is only a single provider. I'd like to hardwire the login sequence to use the single provider's authorization server. - I couldn't customize the authorization requests. The configuration didn't use my authorization resolver. I have to add custom properties. - There would not be a logout event, as far as I know. I have not seen a logout in the Fhir spec. I would have to follow up with the development team of Fhir to confirm.

Comment From: sjohnr

Hi @jjfraney-cg, I think we're getting into the territory of questions that would probably be best for stack overflow, as my intention was to discover whether the use case involved here would require changes to the framework. So far, it doesn't appear that they do, but it sounds like we might need to prove that by working through some of the questions you've brought up. If you wouldn't mind opening a stack overflow question with these points and linking to it here, I can work with you on getting an answer.

My app doesn't have user context. All anonymous. No login/logout.

I think oauth2login may be better than oauth2client configuration.

What I can say for certain is that you are on the right track here. Having an anonymous user doesn't work well with http.oauth2Client() because the authorized clients are tied to the principal name. So in your case, it would always be something like "anonymousUser", which isn't what you want.

Comment From: jjfraney-cg

Thank you.

https://stackoverflow.com/questions/71828258/how-can-i-require-consent-for-each-unique-anonymous-user-with-spring-security-oa

Comment From: sjohnr

@jjfraney-cg I have added an answer. Once you've reviewed it, let's discuss how that impacts this issue and whether it really makes sense to add forwards as stated in the expected behavior:

The developer can override the redirect strategy in configuration to for OAuth2AuthorizationCodeGrantFilter to issue request forward into the existing web context (i.e., a relative path) at the end of authentication.

Comment From: jjfraney-cg

Thanks. I'm sorry I did not have the time to try your solution and respond until now. You made the effort and gave an answer that deserves my earliest attention. Thank you.

This answer does not impact the status of this issue in my view. The AuthenticationSuccessHandler helps with different scenarios.

An AuthenticationSuccessHandler could enable an optional behavior of OAuth2AuthorizationCodeGrantFilter. Not only could it forward instead of the default behavior of redirect, but it can also transfer additional parameters from the authentication response to the redirect or forward uri.

The AuthenticationSuccessHandler may not be absolutely necessary, so I will not strongly advocate for it. I had hacked a copy of Spring's OAuth2AuthorizationCodeGrantFilter to get what I needed. I'm ok with that brute fix.

Comment From: sjohnr

Not a problem, @jjfraney-cg, but I do appreciate the response!

I agree that adding this option to the OAuth2AuthorizationCodeGrantFilter could be helpful. I'll leave this open and see if anyone would like to pick it up and work on it.

Comment From: MrRedstoner

@sjohnr I'd like to take a look at this, my first issue here, what's the final verdict on what all should be done to resolve this? Any other pointers appreciated.

Comment From: sjohnr

@MrRedstoner thanks for volunteering. Take a look at OAuth2AuthorizationCodeGrantWebFilter which is more-or-less already in the state we're trying to get to. If you aren't familiar with the reactive APIs, here's a brief overview of the changes:

  • Add a field of type AuthenticationSuccessHandler
  • Remove the RequestCache and RedirectStrategy fields (this will likely also require introducing an AuthenticationFailureHandler)
  • Initialize the new field in the constructor to SavedRequestAwareAuthenticationSuccessHandler
  • Replace the following lines by using the success handler:

https://github.com/spring-projects/spring-security/blob/33ee3058d4bf5d9dba360531d3e57f5b2574c0d3/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java#L242-L248

That's a very high level attempt to describe the changes. I'm not 100% sure it will work, as there are differences in the WebFilter version. I believe it works under the assumption that the current URI being requested IS the redirectUri, so there's no need to get it from the OAuth2AuthorizationRequest once it's been validated.

Comment From: sjohnr

Another note to the above description, since the setRequestCache() method is public, you would need to use a similar trick as OAuth2AuthorizationCodeGrantWebFilter to keep the method but delegate to the new SavedRequestAwareAuthenticationSuccessHandler instance. That method cannot simply be removed. Hopefully how I described that makes sense.

Comment From: madaster97

Hi there, I'm also interested in using the smart app launch described above with a spring app. Wanted to note that someone wrote an smart on fhir project using Spring Security 5 and Spring v2, and that may help here. For example, in that version they write their own "authorizationRequestResolver" that looks at an unauthenticated request and extracts the "launch".

@sjohnr , for some background, the flow we're going for is a resource-server initiated login. It's only additive on top of OAuth, and in the browser you see the following requests: 0. The user is actively logged into resourceserver.com (After authenticating against the auth server) 1. The user clicks a link to "Launch Client A" 2. The resource server constructs a launch token and navigates to https://clientapp.com/app/launch?launch=abc123&... 3. The client app responds with an authorize request, injecting the launch token into the request 4. The user was already logged into the authz server as part of step 0. They may be asked to re-authenticate, and may be asked to consent to the request. However, we expect most requests would just go through without user interaction, since the click in step 1 could include a consent step 5. Redirect back as usual with an auth code, where the token response includes extra data that was extracted by the auth server from the launch token

Comment From: sjohnr

Thank you for the additional information @madaster97. I haven't yet reviewed the code but the readme of that project looks very interesting, thanks for sharing!