A Simple OAuth2 Client and Server Example: Part I
Thursday, July 11, 2013 |When implementing web site security, OAuth2 almost always comes up. We’ve had requests to implement OAuth2 in the GlassFish REST interface, and, it turns out, I have a similar need on a personal project. Looking at the spec, though, OAuth2 can be pretty daunting. Fortunately, you don’t need to understand it all, and Apache has a project, Oltu (nee Amber) that handles most of the implementation.
Before we get too excited, I need to qualify the statements I’m about to make: I am very new to OAuth2 and, then, by no means an expert. What I’m going to present here is my current understanding of part of what the spec offers. Hopefully, it’s accurate and helpful. As with everything you read on the internet, though, it never hurts to verify. :)
With that out of the way, we’re going to implement a very simple token-based authorization system. This will allow a third-party app to interact with your service on behalf of a user. Here’s the basic flow in English:
-
A user logs in to new application on the web, which we’ll call TheApp. TheApp offers integration with a service you provide, TheService.
-
In order to achieve this integration, TheService must authorize TheApp to act on behalf of TheUser, so TheApp redirects the user to TheService's authorization page.
-
TheUser is presented with the option to grant or deny access to TheApp. He clicks "grant", and is redirected back to TheApp.
-
When TheService redirects back to TheApp, it sends and authorization code. TheApp then takes that authorization code and asks TheService for the access token.
-
TheService verifies the authorization code and returns the token to TheApp.
-
Now, when TheUser asks TheApp to do something with TheService, either directly via the web interface or via a scheduled job, TheApp passes the token as part of the request, which TheService uses to authenticate the request.
-
At any time, TheUser can visit TheService and revoke the token, which will break the integration between TheApp and TheService for that user.
-
At no point does TheApp need to know the username and password for TheUser with TheService.
Still with me? I hope so. I’ve put too much work into this to lose you now! :P
With our basic workflow described, let’s look at an implementation. In this example, TheApp and TheService will be the same app, and TheUser will be unit tests. Let’s start with the authorization code resource:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Path("/authz")
public class AuthzEndpoint {
@Inject
private Database database;
@GET
public Response authorize(@Context HttpServletRequest request)
throws URISyntaxException, OAuthSystemException {
try {
OAuthAuthzRequest oauthRequest =
new OAuthAuthzRequest(request);
OAuthIssuerImpl oauthIssuerImpl =
new OAuthIssuerImpl(new MD5Generator());
//build response according to response_type
String responseType =
oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);
OAuthASResponse.OAuthAuthorizationResponseBuilder builder =
OAuthASResponse.authorizationResponse(request,
HttpServletResponse.SC_FOUND);
// 1
if (responseType.equals(ResponseType.CODE.toString())) {
final String authorizationCode =
oauthIssuerImpl.authorizationCode();
database.addAuthCode(authorizationCode);
builder.setCode(authorizationCode);
}
String redirectURI =
oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);
final OAuthResponse response = builder
.location(redirectURI)
.buildQueryMessage();
URI url = new URI(response.getLocationUri());
return Response.status(response.getResponseStatus())
.location(url)
.build();
} catch (OAuthProblemException e) {
// ...
}
}
}
While we’ve implemented this as a REST resource, this is the code that would back the authorization page, so it would most likely be, for example, a JSF or CDI managed bean. The user would click "authorize", and the authorize
method would be called. In this case, our client (which we’ll look at in a bit), sends a request with a response type of code, so it’s handled via the block at [1].
Notice that we’re not doing any authentication for the request. If this were a real system, this method would be called in the context of an HTTP session, for which the client would already be authenticated. Probably. :) At any rate, to make things simple, we just return the authorization code via the redirect back to TheApp.
Once TheApp has the authorization code, it can then request the actual token, sometimes referred to as a "bearer token". The server side of that can be implemented like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Path("/token")
public class TokenEndpoint {
@Inject
private Database database;
@POST
@Consumes("application/x-www-form-urlencoded")
@Produces("application/json")
public Response authorize(@Context HttpServletRequest request)
throws OAuthSystemException {
try {
OAuthTokenRequest oauthRequest =
new OAuthTokenRequest(request);
OAuthIssuer oauthIssuerImpl =
new OAuthIssuerImpl(new MD5Generator());
// check if clientid is valid
if (!checkClientId(oauthRequest.getClientId())) {
return buildInvalidClientIdResponse();
}
// check if client_secret is valid
if (!checkClientSecret(oauthRequest.getClientSecret())) {
return buildInvalidClientSecretResponse();
}
// do checking for different grant types
if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE)
.equals(GrantType.AUTHORIZATION_CODE.toString())) {
if (!checkAuthCode(oauthRequest.getParam(OAuth.OAUTH_CODE))) {
return buildBadAuthCodeResponse();
}
} else if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE)
.equals(GrantType.PASSWORD.toString())) {
if (!checkUserPass(oauthRequest.getUsername(),
oauthRequest.getPassword())) {
return buildInvalidUserPassResponse();
}
} else if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE)
.equals(GrantType.REFRESH_TOKEN.toString())) {
// refresh token is not supported in this implementation
buildInvalidUserPassResponse();
}
final String accessToken = oauthIssuerImpl.accessToken();
database.addToken(accessToken);
OAuthResponse response = OAuthASResponse
.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken)
.setExpiresIn("3600")
.buildJSONMessage();
return Response.status(response.getResponseStatus())
.entity(response.getBody()).build();
} catch (OAuthProblemException e) {
OAuthResponse res = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.error(e)
.buildJSONMessage();
return Response
.status(res.getResponseStatus()).entity(res.getBody())
.build();
}
}
// ...
}
This resource is actually a bit more complex. In a fully implemented OAuth2 system, TheApp would have had to register a client ID and a client secret. This done, as best as I can tell, to help control access to the number of apps that can use TheService, as well help prevent given out tokens to anyone except the intended client.
Once the client ID and secret have been validated (which we’ve stubbed out here), we come to the meat of the resource, and the behavior is based on the "grant type" requested by the client (TheApp). The first grant type we check, "code", tells the service that we have an authorization code and would like a token. To make things interesting and mostly functional, I have implemented a simple datastore, called Database
, that is simple a couple of Sets to store valid auth codes and tokens. If the auth code is valid, we continue. Otherwise, we return a BAD_REQUEST
response.
The next grant type we check is "password". One means of acquiring token, in addition to an authorization code, is using a username and password. This can be used, for example, where a mobile app redirects the user to a login page, where the user provides his credentials, which are then used to authenticate to generate the token.
Once we validated the request, we can generate a token (using the Oltu class OAuthIssuer
), which we store in our fake database, then generate an OAuthResponse for the client.
TheApp, now equipped with the bearer token, can store it internally for use on behalf of TheUser. When requests are made to TheService, TheApp includes the token in the Authorization
header:
1
Authorization: Bearer <token>
The resource must then validate the token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Path("/resource")
public class ResourceEndpoint {
@Inject
private Database database;
@GET
@Produces("text/html")
public Response get(@Context HttpServletRequest request)
throws OAuthSystemException {
try {
// Make the OAuth Request out of this request
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request, ParameterStyle.HEADER);
// Get the access token
String accessToken = oauthRequest.getAccessToken();
// Validate the access token
if (!database.isValidToken(accessToken)) {
// Return the OAuth error message
OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Common.RESOURCE_SERVER_NAME)
.setError(OAuthError.ResourceResponse.INVALID_TOKEN)
.buildHeaderMessage();
//return Response.status(Response.Status.UNAUTHORIZED).build();
return Response.status(Response.Status.UNAUTHORIZED)
.header(OAuth.HeaderType.WWW_AUTHENTICATE,
oauthResponse
.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE))
.build();
}
// [1]
return Response.status(Response.Status.OK)
.entity(accessToken).build();
} catch (OAuthProblemException e) {
// Check if the error code has been set
// Build error response....
}
}
}
There’s quite a bit of boilerplate code there to validate the access token. It’s not until [1] that we actually do the work the resource was written to do (which is, in this case, simply returning the accessToken). Clearly, that’s too much work to be repeated, so that really should be factored out. For our purposes here, though, I’ll leave that as an exercise for the reader. If you watch the git repo for this example, though, you should find a solution for this at some point. :)
That about covers the server side. In the next post, we’ll cover TheUser, which are the unit tests that drive/test our implementation.