Announcement Announcement Module
Collapse
No announcement yet.
Reusing access tokens across service layers Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Reusing access tokens across service layers

    I have a rest api resource that uses spring security oauth2 for authorization. This is an external api for third party clients to use. This resource will be making calls to internal resources that are only used by our applications - no third party access. These internal resources use method level security for fine grained access control. I am using oauth2 on the internal services to provide a security context with the current resource owner (user) so that the spring method level security works.

    In my external resource, I am configuring an OAuth2RestTemplate as follows to pass the access token to the internal resource:

    Code:
    OAuth2ProtectedResourceDetails resource; //injected
    
    OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication();
    OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)auth.getDetails();
    OAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(details.getTokenValue());
    OAuth2ClientContext clientContext = new DefaultOAuth2ClientContext(accessToken);
    RestTemplate restTemplate = OAuth2RestTemplate(resource,clientContext);
    I have a third party client defined in my oauth client details with some scopes that make sense in the external api. I am also giving it access to both the external and internal resources. I then have an internal client defined (which is the OAuth2ProtectedResourceDetails above) which has access to the internal resources and has scopes that make sense in the internal resources.

    The issue is that I would like to have some special scope (SCOPE_INTERNAL?) that applies to all of the internal resources and that the internal client has when calling an internal resource. The issue is that the only way I can get this to work is to give the third party client the INTERNAL scope, in addition to the existing scopes for accessing the external resource. (I also need to define both the internal and external resource ids for the third party client, but that isn't really visible to them.) The internal services aren't accessible externally due to network configuration, but I still don't like granting these extra scopes to third party clients. The other slightly cleaner solution is to remove all scopes from the internal services. Is there some way I can widen the scope on the access token when it is used by the internal client. I have considered using the TokenStore to create a new access token for the user with the desired scope, but I am not sure if this is the best solution. Since my goal is simply to access the internal services as a particular user, maybe it makes sense to implement some custom authentication in the internal services and drop oauth altogether for internal resources? Any recommendations?

  • #2
    I think it makes sense for the internal communication to have a separate token. Since they operate on behalf of a user I would say that OAuth is a good choice, but if you prefer to roll your own there's nothing stopping you. Presumably your internal resources trust each other, and are trusted by the auth server, so they can authenticate a user without knowing the password, and that's the approach I would take. The implicit grant type is easy to adapt to this pattern - you just need a custom authentication manager on your /oauth/authorize endpoint that recognizes requests from a trusted resource (e.g. with an OAuth2 client_credentials token or basic auth using clilent credentials). We do this in Cloud Foundry as it happens (the "login" client is the Login Server and it can authenticate a user and obtain a token without a password - we use a custom request matcher to recognize the form of the request parameters coming into teh POST to /oauth/authorize). The code is on github if you want to look - if you can't find it ping me back.

    Comment


    • #3
      Thanks for your reply and suggestions. I will have a look at that code and go from there.

      Comment


      • #4
        More details

        So I've spent some time looking through the cloud foundry login-server and uaa code. I'm not totally sure I am understanding your suggestion correctly. Hopefully you can shed more light.

        I've modified my authorization server configuration, adding a new sec:http block:

        Code:
        <http request-matcher-ref="loginAuthorizeRequestMatcher" create-session="always" entry-point-ref="oauthAuthenticationEntryPoint"
                  authentication-manager-ref="loginAuthenticationMgr" xmlns="http://www.springframework.org/schema/security">
                <intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
                <custom-filter ref="oauthResourceAuthenticationFilter" position="PRE_AUTH_FILTER" />
                <custom-filter ref="loginAuthenticationFilter" position="FORM_LOGIN_FILTER" />
                <anonymous enabled="false" />
                <access-denied-handler ref="oauthAccessDeniedHandler" />
            </http>
        
            <bean id="loginAuthorizeRequestMatcher" class="com.acme.oauth2.mvc.AcmeRequestMatcher">
                <constructor-arg value="/oauth/authorize" />
                <property name="accept" value="application/json" />
                <property name="parameters">
                    <map>
                        <entry key="source" value="login" />
                    </map>
                </property>
            </bean>
        
            <oauth:resource-server id="oauthResourceAuthenticationFilter" token-services-ref="tokenServices"
                                   resource-id="oauth" entry-point-ref="oauthAuthenticationEntryPoint" />
        
            <bean id="loginAuthenticationMgr" class="com.acme.oauth2.LoginAuthenticationManager">
                <property name="userDetailsService" ref="userDetailsService"/>
            </bean>
        
            <bean id="loginAuthenticationFilter" class="com.acme.oauth2.AcmeAuthenticationFilter">
                <constructor-arg ref="loginAuthenticationMgr" />
                <property name="parameterNames">
                    <list>
                        <value>login</value>
                        <value>username</value>
                        <value>given_name</value>
                        <value>family_name</value>
                        <value>email</value>
                    </list>
                </property>
            </bean>
        This was taken from https://github.com/cloudfoundry/uaa/...r-security.xml. Most of the implementations above are basically the same as what is in uaa.

        Then I make a request to /oauth/token with grant_type=client_credentials using my trusted client credentials and get a token back.

        Next I post the following to /oauth/authorize using the token I received in the previous step:

        Code:
        "response_type=token&source=login&username=<username>"
        I expected at this point to receive a new token back for the specified user. Instead I receive an error from the AuthorizationEndpoint that a client id must be specified, which I would not expect since at this point the client is authenticated. Do I need to the client_id to the request in the authentication manager? After adding the client_id parameter to the request parameters, I get a further error that the redirect_uri must be specified. In my case I am not sure what a valid redirect_uri would be. After adding a bogus one I get this response (instead of a token):

        Code:
        {"username":"<username>","scope":"internal","response_type":"token","source":"login","redirect_uri":"/bogus","client_id":"internalConfigApiClient","authorizationRequest":{"scope":["internal"],"resourceIds":["oauth","internalconfigapi"],"approved":false,"authorities":[{"authority":"ROLE_CLIENT"}],"authorizationParameters":{"username":"<username>","response_type":"token","scope":"internal","source":"login","redirect_uri":"/bogus","client_id":"internalConfigApiClient"},"approvalParameters":{},"state":null,"clientId":"internalConfigApiClient","responseTypes":["token"],"redirectUri":"/bogus","denied":true}}
        Any guidance or clarification would be really appreciated.

        Comment


        • #5
          The spec says that requests to /oauth/authorize must contain a client id and a redirect URI. You can use a bogus one if the client registration doesn't contain any redirect uris, but that isn't recommended in general (although in this specific case I suppose it doesn't matter). I would expect you would use a different client id for the token you are getting in this step, but I suppose again that it doesn't matter. So all that is working as expected.

          Your remaining stumbling block is just that the request for authorization has to be approved (by a user in general) so the server is sending you back some nice JSON that you could use to render an approval page. Since this is a trusted machine client you don't need the approval so you can override it with a custom UserApprovalManager (there is one in sparklr that does something similar). Just look at the client id in the approval manager and approve if it matches a whitelist would do.

          Comment


          • #6
            Great, that worked. Thanks so much for your help.

            Comment

            Working...
            X