Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OAuth2: Confirmation Approval is not working, Denying even when I click Approve button

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>
like image 615
Alex Man Avatar asked Nov 16 '17 05:11

Alex Man


Video Answer


1 Answers

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.

like image 153
fateddy Avatar answered Oct 12 '22 22:10

fateddy