Coming Up for Air

A Simple OAuth2 Client and Server Example: Part II

In the last post, we took a look at the server side of our OAuth2 system. In this post, we’ll take a quick look at the unit tests that will act as TheUser.

Let’s get right to the code:

@RunAsClient
public class AuthTest extends Arquillian {

    @ArquillianResource
    private URL url;
    private Client client = JerseyClientBuilder.newClient();

    @Deployment
    public static WebArchive createDeployment() {
        WebArchive archive = ShrinkWrap.create(WebArchive.class)
                .addPackages(true, "com.steeplesoft.oauth2")
                .addAsWebInfResource(
                    new FileAsset(
                        new File("src/main/webapp/WEB-INF/beans.xml")),
                            "beans.xml")
                .addAsWebInfResource(
                    new FileAsset(
                        new File("src/main/webapp/WEB-INF/web.xml")),
                            "web.xml")
                .addAsLibraries(Maven.resolver()
                    .loadPomFromFile("pom.xml")
                    .importRuntimeDependencies()
                    .resolve()
                    .withTransitivity()
                    .asFile());
        return archive;
    }

    @Test
    public void authorizationRequest() {
        try {
            Response response = makeAuthCodeRequest();
            Assert.assertEquals(Status.OK.getStatusCode(),
                response.getStatus());

            String authCode = getAuthCode(response);
            Assert.assertNotNull(authCode);
        } catch (OAuthSystemException |
                URISyntaxException |
                JSONException ex) {
            Logger.getLogger(AuthTest.class.getName())
                .log(Level.SEVERE, null, ex);
        }
    }

    @Test
    public void authCodeTokenRequest() throws OAuthSystemException {
        try {
            Response response = makeAuthCodeRequest();
            Assert.assertEquals(Status.OK.getStatusCode(),
                response.getStatus());

            String authCode = getAuthCode(response);
            Assert.assertNotNull(authCode);
            OAuthAccessTokenResponse oauthResponse =
                makeTokenRequestWithAuthCode(authCode);
            assertNotNull(oauthResponse.getAccessToken());
            assertNotNull(oauthResponse.getExpiresIn());
        } catch (OAuthSystemException |
                URISyntaxException |
                JSONException |
                OAuthProblemException ex) {
            Logger.getLogger(AuthTest.class.getName())
                .log(Level.SEVERE, null, ex);
        }
    }

    @Test
    public void directTokenRequest() {
        try {
            OAuthClientRequest request = OAuthClientRequest
                    .tokenLocation(url.toString() + "api/token")
                    .setGrantType(GrantType.PASSWORD)
                    .setClientId(Common.CLIENT_ID)
                    .setClientSecret(Common.CLIENT_SECRET)
                    .setUsername(Common.USERNAME)
                    .setPassword(Common.PASSWORD)
                    .buildBodyMessage();

            OAuthClient oAuthClient =
                new OAuthClient(new URLConnectionClient());
            OAuthAccessTokenResponse oauthResponse =
                oAuthClient.accessToken(request);
            assertNotNull(oauthResponse.getAccessToken());
            assertNotNull(oauthResponse.getExpiresIn());
        } catch (OAuthSystemException |
                OAuthProblemException ex ) {
            Logger.getLogger(AuthTest.class.getName())
                .log(Level.SEVERE, null, ex);
        }
    }

    @Test
    public void endToEndWithAuthCode() {
        try {
            Response response = makeAuthCodeRequest();
            Assert.assertEquals(Status.OK.getStatusCode(),
                response.getStatus());

            String authCode = getAuthCode(response);
            Assert.assertNotNull(authCode);

            OAuthAccessTokenResponse oauthResponse =
                makeTokenRequestWithAuthCode(authCode);
            String accessToken = oauthResponse.getAccessToken();

            URL restUrl = new URL(url.toString() + "api/resource");
            WebTarget target = client.target(restUrl.toURI());
            String entity = target.request(MediaType.TEXT_HTML)
                    .header(Common.HEADER_AUTHORIZATION, "Bearer " +
                        accessToken)
                    .get(String.class);
            System.out.println("Response = " + entity);
        } catch (MalformedURLException |
                URISyntaxException |
                OAuthProblemException |
                OAuthSystemException |
                JSONException ex) {
            Logger.getLogger(AuthTest.class.getName())
                .log(Level.SEVERE, null, ex);
        }
    }

    void testValidTokenResponse(HttpURLConnection httpURLConnection)
            throws Exception {
        InputStream inputStream;
        if (httpURLConnection.getResponseCode() == 400) {
            inputStream = httpURLConnection.getErrorStream();
        } else {
            inputStream = httpURLConnection.getInputStream();
        }
        String responseBody = OAuthUtils.saveStreamAsString(inputStream);
        assert (Common.ACCESS_TOKEN_VALID.equals(responseBody));
    }

    private Response makeAuthCodeRequest() throws OAuthSystemException,
            URISyntaxException {
        OAuthClientRequest request = OAuthClientRequest
                .authorizationLocation(url.toString() + "api/authz")
                .setClientId(Common.CLIENT_ID)
                .setRedirectURI(url.toString() + "api/redirect")
                .setResponseType(ResponseType.CODE.toString())
                .setState("state")
                .buildQueryMessage();
        WebTarget target = client.target(new URI(request.getLocationUri()));
        Response response = target.request(MediaType.TEXT_HTML).get();
        return response;
    }

    private String getAuthCode(Response response) throws JSONException {
        JSONObject obj = new JSONObject(response.readEntity(String.class));
        JSONObject qp = obj.getJSONObject("queryParameters");
        String authCode = null;
        if (qp != null) {
            authCode = qp.getString("code");
        }

        return authCode;
    }

    private OAuthAccessTokenResponse
            makeTokenRequestWithAuthCode(String authCode)
        throws OAuthProblemException, OAuthSystemException {
        OAuthClientRequest request = OAuthClientRequest
                .tokenLocation(url.toString() + "api/token")
                .setClientId(Common.CLIENT_ID)
                .setClientSecret(Common.CLIENT_SECRET)
                .setGrantType(GrantType.AUTHORIZATION_CODE)
                .setCode(authCode)
                .setRedirectURI(url.toString() + "api/redirect")
                .buildBodyMessage();
        OAuthClient oAuthClient =
            new OAuthClient(new URLConnectionClient());
        OAuthAccessTokenResponse oauthResponse =
            oAuthClient.accessToken(request);
        return oauthResponse;
    }
}

The first thing you should notice is that we’re using TestNG and Arquillian. I won’t go into the details on the Arquillian set up here, other than to note that we need our test to @RunAsClient, and to point out the @Deployment method that builds our test archive for us.

Moving on to authorizationRequest, we can see (in makeAuthCodeRequest) how the Oltu library makes it easy to build the request for an authorization code. Utlimately, the library helps use create the request URI, which we then pass to the JAX-RS client as it makes the actual request. To be honest, there’s a bit here (such as the state field) that I don’t understand. Any expert help here would be appreciated. :)

The next method, authCodeTokenRequest, shows the flow of getting an authorization code, then using it to get the access token. That’s followed by an example of a direct request for token via the password grant type. Finally, we have an end to end example, from authorization code to accessing our protected resource.

That’s all there is to it. As you can see in the POM and arquillian.xml, the only container currently supported is GlassFish, which the tests expect to find in glassfish4/ in the project’s base directory. Once that’s installed, the tests can be run with the normal mvn test.

If you have any questions about the code, I can try to answer them, but as should be clear by now, I’m still learning all of this. If I’ve made any mistakes in the code or my description of the protocol, please don’t be shy about correcting me. We’re all hear to learn. :)

Quotes

Sample quote

Quote source