5

I have created an application using Spring integrated Oauth2. I have my own custom login and authorize templates. After successfully authenticated it is redirecting to authorize .html where it asks for the user approval. The issue is that when I click even Approve or Deny button action is always DENIED like as shown in my table below

enter image description here

Also How can we enable REST based authentication and authorization using oauth2. I tried disabling the csrf for enabling users to do authentication and authorization but still didn't work.

Can anyone please help me on this.

You can download and see the Complete Application from here (Updated as per the last suggestion 19/11/2017)

UPDATE 1

As per the suggestion from @fateddy I have used Option 3 using the ApprovalStoreUserApprovalHandler. I have used the exact authorize.html given.

Lets say I am having two clients (client123 and client789) in my database.

Client client123 which does not have auto-approve enabled and the client client789 with auto-approve option enabled for openid scope.

Now the problem is that I am getting the below exception for client123 when I click the approve button.

error="invalid_client", error_description="Bad client credentials"

OAuth2Config.java is as given below

@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    
    @Autowired
    private DataSource dataSource;

    @Bean
    public UserApprovalHandler userApprovalHandler() {
        ApprovalStoreUserApprovalHandler userApprovalHandler= new ApprovalStoreUserApprovalHandler();
        userApprovalHandler.setApprovalStore(approvalStore());
        userApprovalHandler.setClientDetailsService(clientDetailsService());
        userApprovalHandler.setRequestFactory(requestFactory());
        return userApprovalHandler;
    }
    
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Bean
    public DefaultOAuth2RequestFactory requestFactory(){
        return new DefaultOAuth2RequestFactory(clientDetailsService());
    }
    
    @Bean
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }
    
    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //endpoints.tokenStore(tokenStore());
       // endpoints.approvalStore(approvalStore());
        endpoints.userApprovalHandler(userApprovalHandler());        
        endpoints.authorizationCodeServices(authorizationCodeServices());
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }
    
    @Override
    public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
        authorizationServerSecurityConfigurer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
        
    }  
}

authorize.html

<html>
<head>
</head>
<body>
    <div class="container">
        <h2>Please Confirm</h2>

        <p>
            Do you authorize "${authorizationRequest.clientId}" at "${authorizationRequest.redirectUri}" to access your protected resources
            with scope ${authorizationRequest.scope?join(", ")}.
        </p>
        <form id="confirmationForm" name="confirmationForm" action="/auth/oauth/authorize" method="post">
            <input name="scope.openid" value="true" type="checkbox" /> Read<br>
            <button class="btn btn-primary" type="submit">Approve</button>
        </form>
    </div>
</body>
</html>
Community
  • 1
  • 1
Alex Man
  • 4,746
  • 17
  • 93
  • 178
  • Does the post-request ever reach `org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint#approveOrDeny()`? – fateddy Nov 16 '17 at 10:37
  • @fateddy Thanks for the reply, where to check that whether it reaches `appr‌​oveOrDeny()`. Can you please guide me on that – Alex Man Nov 16 '17 at 10:45
  • Assuming that you are using an IDE: start the app using the debug-mode, search for the mentioned class+method, set the breakpoint (anywhere at the beginning of the method), switch to your browser and repeat the authorization process. If it hits the endpoint the IDE will halt at the breakpoint. See here https://www.jetbrains.com/help/idea/debugging-your-first-java-application.html (or just consult google) – fateddy Nov 16 '17 at 10:50
  • yes it is entering in `approveOrDeny` – Alex Man Nov 16 '17 at 11:12
  • when I debug `{user_oauth_approval=true, _csrf=32e372ac-af12-4393-bc91-c6e2d4ce60f2}` where `user_oauth_approval` is true but still why it is DENIED – Alex Man Nov 16 '17 at 11:17
  • hard to tell - just debug deeper into the `ApprovalStoreUserApprovalHandler` - the issue must be somewhere in this area. – fateddy Nov 16 '17 at 16:52
  • Double check your roles, if the user doesn't have the correct roles, and your application is expecting certain roles, you can still get denied even if you approved. – sksallaj Nov 18 '17 at 08:08
  • @sksallaj even the roles are fine. you can check the complete application from [**here**](https://bitbucket.org/nidhishkrishnan/oauth2-spring-example/src) – Alex Man Nov 18 '17 at 11:43

1 Answers1

5

Reference is the provided project, git-commit 972b85. At the end you have several options. But let's have a look at the current project state.

The authorize-endpoint (/oauth/authorize) let's the user decide whether to authorize or deny access (by displaying a form). The UserApprovalHandler then decides whether to grant authorization or not.

The existing UserAppovalHandler-implementations require different request-params in order to be able to make a decision - that also means that this has an impact on what the /oauth/authorize-view has to look like.

Option 1

The customized /oauth/authorize-view contains <input name="user_oauth_approval" value="true" /> which requires a UserApprovalHandler that picks up said parameter to make a decision. Using the DefaultUserApprovalHandler (which does not remember any decisions) will work. Here's what the configuration might look like. An Approval-Store is not needed in this case.

<form id="confirmationForm" name="confirmationForm"
        action="/auth/oauth/authorize" method="post">
   <input name="user_oauth_approval" value="true" type="hidden" />
   <input type="hidden"  name="${_csrf.parameterName}" value="${_csrf.token}"/>
   <button class="btn btn-primary" type="submit">Approve</button>
</form>

The user_oauth_approval=true request-parameter is only picked up if the DefaultUserUserApprovalHandler is used:

@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

   @Bean
   UserApprovalHandler userApprovalHandler() {
      return new DefaultUserApprovalHandler();
   }

   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      // ...
      endpoints.userApprovalHandler(userApprovalHandler());        
   }
}

Option 2

by following Option 1 but in this case by providing a custom UserApprovalHandler that remembers any decisions.

Option 3

Sticking with the ApprovalStoreUserApprovalHandler (which uses a TokenStore underneath) requires some adaptions to the form:

<form id="confirmationForm" name="confirmationForm" action="/auth/oauth/authorize" method="post">
    <!-- 
    The ApprovalStoreUserApprovalHandler tests scopes by testing request-params prefixed with `scope.*` 
    For dynamic input-element rendering one might iterate over
    ${authorizationRequest.scope}

    Provides access to the scope=openid whenever the user checks the checkbox:
    -->
    <input name="scope.openid" value="true" type="checkbox" /> OpenID<br>
    <input type="hidden"  name="${_csrf.parameterName}" value="${_csrf.token}"/>
    <button class="btn btn-primary" type="submit">Approve</button>
</form>

The Auth-Server-Config:

@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

   @Bean
   public TokenStore tokenStore() {
      return new JdbcTokenStore(dataSource);
   }

   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      // ...
      // registering an ApprovalStore automaticaly bootstraps `ApprovalStoreUserApprovalHandler`
      endpoints.approvalStore(approvalStore());
   }
}

Option N

There might be other options that might be a good fit - but that depends on your requirements.

fateddy
  • 6,887
  • 3
  • 22
  • 26
  • Thanks for the reply, I have tried with `ApprovalStoreUserApprovalHandler` but got **error="invalid_client", error_description="Bad client credentials"**. I have committed the updated code to [**bitbucket**](https://bitbucket.org/nidhishkrishnan/oauth2-spring-example/src) – Alex Man Nov 19 '17 at 16:22
  • Can you please take a look at my **UPDATE 1** – Alex Man Nov 19 '17 at 16:58
  • What exactly do you mean by `Update 1`? The error itself is pretty clear - the client(=application) is not able to get access because the client-credentials used are incorrect (either password or username or both are incorrect). – fateddy Nov 19 '17 at 18:18
  • Had a look at it once again: 1) the login form action-URI should be `/auth/login` (where `/auth` denotes the context-path). 2) the redirect URI defined for `client789` should redirect to the client application - eg. `http://localhost:8080/client` (not to the auth-server itself). Have a look at the specification - `implicit`-flow: https://tools.ietf.org/html/rfc6749#section-1.3.2 When testing the applications on the same hostname (`localhost`) make sure that both auth-server and client application use a different context path in order to avoid any cookie-path clashes. – fateddy Nov 19 '17 at 20:14
  • I have changed form action url from `login` to `/auth/login`, I signed in with correct username and password and came to authorize page, If you observe this [screenshot](https://i.stack.imgur.com/z4C2o.png) client_id which is passing through url and body is **client123** which is also correct but still getting the same `error="invalid_client", error_description="Bad client credentials"` – Alex Man Nov 20 '17 at 01:49
  • What grant-type are you using? `authorization_code`? The `implicit`-grant does not require client-authorization... (look at the spec linked in the comment above) – fateddy Nov 20 '17 at 06:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/159365/discussion-between-alex-man-and-fateddy). – Alex Man Nov 20 '17 at 06:54
  • Hi @fateddy any idea on this issue https://stackoverflow.com/questions/49229551/spring-oauth2-0-getting-user-roles-based-on-client-id – Alex Man Mar 12 '18 at 07:00