Page 2 of 3 FirstFirst 123 LastLast
Results 11 to 20 of 21

Thread: using s2-oauth provider for sign-on: one-time grant prompt and returning same token

  1. #11
    Join Date
    Sep 2006
    Location
    Hartford, CT
    Posts
    145

    Default

    Full disclosure... I work with Tony.

    If I may contribute to the discussion...

    Looking at tonr's integration with sparklr as an example, one would see that tonr does not support anything such as "sign in using sparklr." There is the option to connect to sparkl only after the user has signed in to tonr using a local tonr account. In a case such as this, the client application (tonr) can store the token that the user approved and the provider issued. On subsequent user visits to tonr, tonr can continue to communicate with sparklr using this token that it has stored in association to that user/provider tuple. One notable thing here is that the OAuth dance occurs ONLY once and ONLY one token is issued.

    Contrast this, however to the "sign in with sparklr" use case that Tony is inquiring about. tonr does not "know" who the user is yet (or else tonr would not be asking the user to sign in, right?) Even if tonr has retained a token for the user/provider tuple in question, that token is of no consequence at this point. Authentication is carried out by a REPETITION of the OAuth dance.

    The problem is that SSOAuth's out-of-the-box components do not feature any convenient mechanism whereby the provider can acknowledge during the dance that a token has ALREADY been approved and issued for the user/client tuple. Therefore, every repetition of the OAuth dance ultimately results in a new token. Issuing a new token- of course- would require the user to approve it.

    Besides cultivating a poor user experience, this also results in an completely unnecessary accumulation of tokens on the provider side that have been issued to the same user/client tuple. If a user has signed into tonr via an OAuth dance with sparklr 100 times, then 100 tokens have been issued. Not only is it unnecessary, but it also diminishes the user experience of a "revoke token" use case. If a user wishes to revoke tonr's access, they'd have a hundred tokens to revoke.

    To streamline this, the provider (auth server) should ideally recognize this condition and bypass the access confirmation page. (Heavy hitters like Facebook do this.) I don't feel that hacking on the controller that backs the access confirmation is an ideal means to implement this optimization. If SSOAuth is implementing the "dance," then SSOAuth, IMO, ought to account for this.

    And this says nothing of the AuthorizationServerTokenServices interface not supporting operations as fundamental as "find tokens by user id" (necessary to support the revoke token use case) or "find token by user id and client id" (necessary to acknowledge cases where a suitable token has ALREADY been issued to support the use case described above).

    Again... just as a sanity check, if the OAuth dance is done repeatedly with Facebook with the same user/client combination, not only is the user not pestered repeatedly to approve a token, but the existing, approved token is also re-issued. This seems like reasonable behavior, and I would humbly submit that this should be "sensible default" behavior for SSOAuth.
    Kent Rancourt
    DevOps Engineer

  2. #12
    Join Date
    Jun 2005
    Posts
    4,230

    Default

    Quote Originally Posted by krancour View Post
    To streamline this, the provider (auth server) should ideally recognize this condition and bypass the access confirmation page. (Heavy hitters like Facebook do this.) I don't feel that hacking on the controller that backs the access confirmation is an ideal means to implement this optimization. If SSOAuth is implementing the "dance," then SSOAuth, IMO, ought to account for this.
    I think part of the confusion comes from the way you describe the use case - I don't see it as bypassing the user approval page (which is a possible customization we discussed above), but as re-using existing tokens, which (as you point out) seems like a sensible idea as a default.

    Have you looked at the code recently? We made some changes last week that allow the token service implementations we provide to re-use existing tokens. I think your use case should work out of the box, but I'm happy to look closer if you can provide test cases, or specific implementation details. If there are problems with the implementation we have you can always contribute code to fix it.

    And this says nothing of the AuthorizationServerTokenServices interface not supporting operations as fundamental as "find tokens by user id" (necessary to support the revoke token use case) or "find token by user id and client id" (necessary to acknowledge cases where a suitable token has ALREADY been issued to support the use case described above).
    True, but that's a different use case (and not covered by the spec). There is a JIRA ticket open for revocation and token management, so feel free to contribute ideas and code there.

    Again... just as a sanity check, if the OAuth dance is done repeatedly with Facebook with the same user/client combination, not only is the user not pestered repeatedly to approve a token, but the existing, approved token is also re-issued. This seems like reasonable behavior, and I would humbly submit that this should be "sensible default" behavior for SSOAuth.
    As I hope it is. If not contributions are gratefully accepted. Thanks for all your analysis.

  3. #13
    Join Date
    Sep 2006
    Location
    Hartford, CT
    Posts
    145

    Default

    Thanks for the reply, Dave. I'll give M5 a closer look. Admittedly, many of my comments are based on M4.

    I don't see it as bypassing the user approval page (which is a possible customization we discussed above), but as re-using existing tokens, which (as you point out) seems like a sensible idea as a default.
    As for the notion of "bypassing" the access confirmation page- that was actually a poor word choice on my part.

    As I see it, access confirmation is a step in the dance that needs to occur when a NEW token is issued. But if existing tokens are reused/reissued, then the user has already given their approval, and there logically (I think) would be no approve/reject step in that case. I think we might be on the same page, but just articulating it differently.

    I guess what I'm not entirely clear on yet is this... it all comes back to the AuthorizationServerTokenServices that I previously mentioned. Without methods on that interface to find a token by user id / client id pair, I have difficulty understanding how the "bypass" optimization discussed above could actually be implemented. Obviously, we could extend that interface and use it in our own code, but SSOAuth code (e.g. components that execute the dance- filters in the SS filter chain, etc.) are obviously written against the AuthorizationServerTokenServices interface and it's not immediately obvious to me where/how to customize those components to use our extended interface to locate an existing token and decide to "bypass" access confirmation.

    I'm sure a deeper dive into the SSOAuth code than we've already gone will be enlightening, but I feel as if anything we do to get this working is going to be a bit of a hack. We'd have to abandon the custom config namespace and start using traditional bean definitions in order to implement the modifications we need. That requires a bit of a feat in terms of reverse engineering the bean definitions that your custom config namespace creates behind the scenes. Bottom line is I feel like this is all totally doable for us... but it would feel like a total hack and wouldn't be easily repeatable on our future projects. Rather than hack, I think we'd much rather work with you to see if there are any better options.

    Can you send a link to the JIRA issue you mentioned? I think the modifications to AuthorizationServerTokenServices to support a revoke token use case overlap pretty significantly with what we're discussing above.
    Kent Rancourt
    DevOps Engineer

  4. #14
    Join Date
    Sep 2006
    Location
    Hartford, CT
    Posts
    145

    Default

    Kent Rancourt
    DevOps Engineer

  5. #15
    Join Date
    Jun 2005
    Posts
    4,230

    Default

    Glad that we are converging. Some more comments on your analysis:

    The changes I'm talking about are recent (since M5). And the "re-use tokens" use case is covered by internal implementation details of the AuthorizationServerTokenServices, so its interface hasn't changed (the TokenStore interface did if you want to customize or validate the change). I don't really see token search and revocation by users as the same thing, and while the existing token service implementations will probably be extended to cover those use cases, I see it as a new interface - UserTokenServices, maybe?

  6. #16
    Join Date
    Jul 2005
    Posts
    108

    Default

    hi dave,

    i grabbed a recent M5+ version from github maybe a week ago, and tried out some of these ideas.

    looks like the latest code base is being a good citizen w.r.t. not creating new tokens when there is an existing one, so nice work on that!

    i have been working on attempting to hook as discussed above such that the client-user will only get prompted once for consent, and then subsequently will get whisked through without a consent prompt as long as there is an appropriate existing token.

    i'm attempting to do this in a provider based on sparklr2 using the following altered version of the provided AccessConfirmationController:

    Code:
    	
    package org.springframework.security.oauth.examples.sparklr.mvc;
    
    import java.security.Principal;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.oauth2.common.OAuth2AccessToken;
    import org.springframework.security.oauth2.provider.AuthorizationRequest;
    import org.springframework.security.oauth2.provider.ClientDetails;
    import org.springframework.security.oauth2.provider.ClientDetailsService;
    import org.springframework.security.oauth2.provider.OAuth2Authentication;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.SessionAttributes;
    
    /**
     * Controller for retrieving the model for and displaying the confirmation page for access to a protected resource.
     * 
     * @author Ryan Heaton
     */
    @Controller
    @SessionAttributes(types = AuthorizationRequest.class)
    public class AccessConfirmationController
    {
      private static final Logger log = LoggerFactory.getLogger(AccessConfirmationController.class);
      private ClientDetailsService clientDetailsService;
      private TokenStore tokenStore;
    
      @RequestMapping("/oauth/confirm_access")
      public String getAccessConfirmation(@ModelAttribute AuthorizationRequest authorizationRequest, Model model, Principal principal)
          throws Exception
      {
        // if there is already a token granted for this client/user/resource/scope quadruple, then 'short-circuit' confirmation page.
        //
        // current strategy for determining this is to use implementation of TokenStore.getAccessToken(OAuth2Authentication) which
        // uses DefaultAuthenticationKeyGenerator which in turn accounts for the client/user/resource/scope quadruple.
        //
        // current 'short-circuit' strategy is to forward to a page (based on the original access_confirmation.jsp)
        // which does a javascript based 'auto-post' to simulate user accepting on consent page...
    
        // at this point, authorizationRequest doesn't have resource-ids because they aren't passed on the request,
        // but rather stashed with the client info, so we need to dig up ClientDetails, grab resource-ids and create
        // a new AuthorizationRequest based on the original but augmented with resource-ids.
        //
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
    
        authorizationRequest = new AuthorizationRequest(clientDetails.getClientId(), authorizationRequest.getScope(),
            clientDetails.getAuthorities(), clientDetails.getResourceIds());
    
        UsernamePasswordAuthenticationToken userAuthentication = new UsernamePasswordAuthenticationToken(principal, null, null);
        OAuth2Authentication oa2Authentication = new OAuth2Authentication(authorizationRequest, userAuthentication);
        OAuth2AccessToken oa2AccessToken = tokenStore.getAccessToken(oa2Authentication);
    
        String viewName = null;
    
        if (oa2AccessToken != null)
        {
          // already issued a token for this client/user/resource/scope quadruple, so no need to prompt for access again,
          // so initiate short-circuit strategy
          //
          viewName = "forward:/auto-confirm";
        }
        else
        {
          model.addAttribute("auth_request", authorizationRequest);
          model.addAttribute("client", clientDetails);
          viewName = "access_confirmation";
        }
    
        return viewName;
      }
    
      @Autowired
      public void setClientDetailsService(ClientDetailsService clientDetailsService)
      {
        this.clientDetailsService = clientDetailsService;
      }
    
      public void setTokenStore(TokenStore tokenStore)
      {
        this.tokenStore = tokenStore;
      }
    }
    i originally tried to avoid the 'auto-submit-form' page by passing control directly to AuthorizationEndpoint.approveOrDeny(), but ran into issues downstream in TokenEndpoint when AuthorizationCodeTokenGranter tossed a RedirectMismatchException because redirect-url wasn't included in the parameters for some reason with this strategy.

    at that point, i was too beat down to investigate further, so i fell back to the clunky (but tried-and-true) 'auto-submit-form' method:

    Code:
    <!DOCTYPE html>
    <html>
    <head>
    <script src="http://code.jquery.com/jquery-latest.min.js"></script>
    <script>
    	$(document).ready(function() {
    		$("#confirmationForm").submit();
    	});
    </script>
    </head>
    
    <body>
    	<form id="confirmationForm" name="confirmationForm"
    		action="<%=request.getContextPath()%>/oauth/authorize" method="post">
    		<input name="user_oauth_approval" value="true" type="hidden" />
    	</form>
    </body>
    
    </html>
    anyway, just wanted to kick my solution out for comments.

    thanks,
    tony.
    Last edited by tony_k; Feb 17th, 2012 at 09:06 PM. Reason: use spaces v tabs in code block to improve readability

  7. #17
    Join Date
    Jul 2005
    Posts
    108

    Default

    i actually just spotted kent's comments from last month, and while i think some of dialog may be rendered moot by improvements introduced in M5, i'll +1 his comment that it would be more convenient if s2-oauth integrated the functionality i introduced into the access-confirmation-controller w.r.t. short-circuiting the consent prompt if a token appropriate to the request already exists.

    i'd even venture to suggest that you could default that behavior to 'true' and allow for that to be customized to 'false' if desired.

  8. #18
    Join Date
    Jun 2005
    Posts
    4,230

    Default

    That controller is provided by the application (not by the framework), but I can definitely get behind changing the sample app if it works better that way. Can you think of a way to abstract the behaviour into a generic contoller that the framework could provide?

  9. #19
    Join Date
    Jul 2005
    Posts
    108

    Default

    hi dave,

    yes, i understand that access-confirmation-controller is part of sparklr v s2-oauth.

    i was thinking that the behavior could be integrated into s2-oauth in the flow
    prior to calling into the a-c-c controller, but admittedly didn't dig into the detail of if/how that could occur.

    i didn't consider the concept of packing up an a-c-c into s2-oauth, but if it makes sense to keep that behavior
    in a discrete controller (v maybe pulling it into one or more existing elements of the framework),
    then i can give it a little more thought. i think it's possible to write that auto-submit page directly into the response
    from the controller, so a discrete page need not exist (some more thought might be required w.r.t. details like
    grabbing jquery from a cdn).

    as i indicated, i was instinctively trying to avoid a trip back to the browser, but the request mapping for AuthorizationEndpoint.approveOrDeny() was configured as a POST, and i ran into that trouble calling it directly, but if i knew you were behind the idea at some level, i could try some things that may be cleaner given some accommodation within the s2-oauth codebase.
    Last edited by tony_k; Feb 18th, 2012 at 07:01 PM. Reason: typo

  10. #20
    Join Date
    Jul 2005
    Posts
    108

    Default

    hi dave,

    i had some success illustrating how this functionality might be integrated directly into s2-oauth.

    i have transferred the discussion and relevant information here as it seemed more appropriate for this jira entry:

    https://jira.springsource.org/browse...#comment-75969

    sorry i'm not more git-savvy just yet, or i would have provided code in git vs attachments to the jira entry.

    can you review when you have an opportunity and comment as appropriate?

    thanks!
    tony.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •