Skip to content

Custom Authentication

Nikita Shchienko edited this page Apr 2, 2019 · 2 revisions

Authentication mechanisms can provide access tokens by key, link, LDAP login and password, etc. REST API uses its own authentication mechanism that cannot be modified. In order to use custom authentication process, you need to create a REST controller and use its URL.

Let’s consider the custom authentication mechanism that enables getting an OAuth token by a promo code. In the following example we will use a sample application that contains the Coupon entity with code attribute. We will send this attribute’s value as an authentication parameter in GET request.

  1. Create a Coupon entity with the code attribute:
@Column(name = "CODE", unique = true, length = 4)
protected String code;
  1. Create a user with promo-user login on behalf of which the authentication will be performed.

  2. Create a new Spring configuration file with name rest-dispatcher-spring.xml under the root package (com.company.demo) of web module. The content of the file must be as follows:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    <context:component-scan base-package="com.company.demo.web.rest"/>

</beans>
  1. Include the file into the cuba.restSpringContextConfig application property in the modules/web/src/web-app.properties file:
cuba.restSpringContextConfig = +com/company/demo/rest-dispatcher-spring.xml
  1. Create the rest package under the root package of web module and implement the custom Spring MVC controller in it. Use the OAuthTokenIssuer bean to generate and issue the REST API token for a user after the custom authentication:
@RestController
@RequestMapping("auth-code")
public class AuthCodeController {
    @Inject
    private OAuthTokenIssuer oAuthTokenIssuer;
    @Inject
    private LoginService loginService;
    @Inject
    private Configuration configuration;
    @Inject
    private DataManager dataManager;
    @Inject
    private MessageTools messageTools;

    // here we check secret code and issue token using OAuthTokenIssuer
    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity get(@RequestParam("code") String authCode) {
        // obtain system session to be able to call middleware services
        WebAuthConfig webAuthConfig = configuration.getConfig(WebAuthConfig.class);
        UserSession systemSession;
        try {
            systemSession = loginService.getSystemSession(webAuthConfig.getTrustedClientPassword());
        } catch (LoginException e) {
            throw new RuntimeException("Error during system auth");
        }

        // set security context
        AppContext.setSecurityContext(new SecurityContext(systemSession));
        try {
            // find coupon with code
            LoadContext<Coupon> loadContext = LoadContext.create(Coupon.class)
                    .setQuery(LoadContext.createQuery("select c from demo$Coupon c where c.code = :code")
                            .setParameter("code", authCode));

            if (dataManager.load(loadContext) == null) {
                // if coupon is not found - code is incorrect
                return new ResponseEntity<>(new ErrorInfo("invalid_grant", "Bad credentials"), HttpStatus.BAD_REQUEST);
            }

            // generate token for "promo-user"
            OAuthTokenIssuer.OAuth2AccessTokenResult tokenResult =
                    oAuthTokenIssuer.issueToken("promo-user", messageTools.getDefaultLocale(), Collections.emptyMap());
            OAuth2AccessToken accessToken = tokenResult.getAccessToken();

            // set security HTTP headers to prevent browser caching of security token
            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.CACHE_CONTROL, "no-store");
            headers.set(HttpHeaders.PRAGMA, "no-cache");
            return new ResponseEntity<>(accessToken, headers, HttpStatus.OK);
        } finally {
            // clean up security context
            AppContext.setSecurityContext(null);
        }
    }

    // POJO for JSON error messages
    public static class ErrorInfo implements Serializable {
        private String error;
        private String error_description;

        public ErrorInfo(String error, String error_description) {
            this.error = error;
            this.error_description = error_description;
        }

        public String getError() {
            return error;
        }

        public String getError_description() {
            return error_description;
        }
    }
}
  1. Exclude the rest package from scanning in web/core modules: the OAuthTokenIssuer bean is available only in REST API context, and scanning for it in the application context will cause an error.
<context:component-scan base-package="com.company.demo">
    <context:exclude-filter type="regex" expression="com\.company\.demo\.web\.rest\..*"/>
</context:component-scan>

Now users will be able to obtain OAuth2 access code using GET HTTP request with the code parameter to

http://localhost:8080/app/rest/auth-code?code=A325

The result will be:

{
  "access_token": "74202587-6c2b-4d74-bcf2-0d687ea85dca","token_type":"bearer",
  "expires_in":43199,
  "scope":"rest-api"
}

The obtained access token should then be passed to REST API, as described in the documentation.

Next: Social Login in REST API

Clone this wiki locally