As service architecture evolves, new services may join your enterprise services landscape. The question, “which service is authorized to run operations in the back-end?”, needs to find an answer. Managing authorization on protected resources without sharing user credentials on whose behalf the operation is being executed, among all services, becomes crucial, but I doubt it is a trivial task. Thus, understanding OAuth 2.0 so as to manage authorization and control user access on REST resources is an essential part of enterprise application development. Fortunately, there are frameworks like Spring Security - and Spring-Security-OAuth in Java stack, which provide a solid foundation, that simplifies this task, but still integration of authorization framework in an enterprise application might be tedious work. Our goal in this article will be understanding the fundamental concepts of the OAuth 2.0 protocol and in addition, step-by-step implementing a backend with its components.
Before we get started with OAuth, I briefly want to brush up on the topic first, “Authentication, Authorization and Access Control”. Authentication verifies your identity. It is the same procedure if you show your ID to authorities so that they are able to know who they are talking to. Authentication by Username/Password pair (and along with 2FA) is a broadly adopted method, today. Authorization is there to ensure if an access is authorized to the requestor, who has already been authenticated, for instance if you possess a driver license you are authorized to drive a car, while access control does manage the permissions/privileges.
OAuth 2.0 in a Nutshell
OAuth 2.0 is an authorization protocol standardized under RFC 6749 within the IETF as an open standard, of which purpose is to specify user-and-client authorization on protected resources without sharing their credentials with each other. The roles defined in OAuth are:
Resource owner, that is the end-user who owns resources of interest, that is hosted by resource server, the client, the application which claims access on resource owner’s (user’s) protected resources and perform some operations on behalf of the user and authorization server which manages user and client authentication and authorization by exchanging user credentials with authorization code and access tokens. The authorization workflow is initiated by the client, that is the application requesting access on resource owner’s resources on behalf of a user, e.g a mobile app. Resource server is the server which hosts the protected resources. It only accepts the requests with a valid access token. The authorization server is the one which issues access tokens (and authorization codes depending on the grant type) to the clients after a successful authentication.
Figure - 1: OAuth 2.0 Workflow with grant type “authorization code”
Access tokens are credentials, that are issued to clients by authorization server, allowing them to access protected resources. Access tokens are valid till they expire and may be effective in a pre-defined scope, in which the client is allowed to operate e.g read-scope, read-write scope, private scope, etc. By using access tokens in client and server communication, we eliminate the need of sharing resource owner’s credentials, so client applications like mobile apps do not need to memorize user’s credentials, at all.
The other token type is the refresh token. Refresh token is only used to obtain a new access token before the access token expires. Once a client application is authorized, there is no need to re-authenticate the user, everytime the access token expires if it has a refresh token. In this case, the client may use the refresh token that is returned from the authorization server along with the access token to obtain a new access token from the authorization server.
On the other hand, the client might be another web service, and in this case the user will be redirected to the authorization server with a client id, an optional scope parameter and a redirection URI so that the user is able to authenticate on their own. A real-life example for this authentication flow is “Facebook integration” on Twitter. Twitter, the client, needs access to protected resources to post your recent tweets on your Facebook timeline. After a successful authentication process against Authorization Server, Facebook, the request will be redirected to the clients redirection URI (to Twitter) with an authorization code with which the client can request an access token from Authorization Server. In this “authorization code” grant, the client access to the user credentials is not permissible.
Scope defines the boundaries of the authorization request. The authorisation server may enforce the clients to specify the scope while requesting access token in which scope the access token should be effective. RFC 6749 3.3 Even though scopes are optional, they are essential in workflows and we will revisit them later.
In the next section, we are going to integrate these foundations with implementation so that resource owners are able to access their resources while unauthorized access can be denied. To do this, we use Spring framework which provides an annotation-based programming model to incorporate OAuth 2.0 concepts with services declaratively.
Building the Backend
So far we have covered the groundwork about OAuth 2.0, that is required to understand the concepts which we use to build our authorization and authentication backend. Next step, we are going to start creating the components like Authorization Server, Resource Server, and so on. The backend, I am giving as example here, is part of a hypothetical book webshop so as to store book reviews. It means, there will be users who require access to write some reviews while other users will only need read access to view the reviews.
The sample application, we are building here, is a Spring Boot Application so we can start the container either by running the Spring Boot Maven plugin in the command line or as Java executable by running the main method, e.g, in an IDE - another approach would be by hitting java jar command if the application has already been packaged in JAR file. Moreover, the application is backed by a MySQL database so that the user credentials can be persisted. I am going to set up a docker container environment to start the entire back-end on my local computer. Dockerization will speed up “development and test cycle” and once we decide to deploy our back-end, it is much easier to roll the services out onto a container orchestration platform like Mesos or Kubernetes.
To build the backend, we will create a new Maven project from scratch while adding Spring Boot parent into the project’s POM which ensures the right versions of dependencies get included. For the sake of ease, I will create a single project which includes both the Resource and the Authorization Server in the same application, but in production, you may want to split them into two separate services, depending on the scale. For this purpose, I used the Maven simple project archetype and the files are organized in classical Maven project structure, i.e we have the Java packages in src/main/java and resources like configurations, etc. in src/main/resources source directories.
Figure - 2 : Project’s structure
The Figure 2 demonstrates the project structure we will work on throughout this article. I organized the classes in packages as follows:
io.ryos.auth is for Spring Boot application classes and configurations.
io.ryos.dao contains the classes for persistence layer, e.g entities, repositories (of Data Access Layer).
io.ryos.data has some value objects like UserProfile as a representation of the user file.
io.ryos.resources is where REST resources are located and the resources source directory to keep classpath files like configurations, properties-files, etc. The project’s POM resides in the root directory along with docker-compose.yml. The POM includes the Spring Boot’s starter parent:
Example - 3 : Parent POM.
We will also need the following dependencies to enable the Spring Framework in the project and some more dependencies mostly pertaining to Java persistence and MySQL driver:
Example - 4 : Project’s POM file.
Note that, the first annotation, ➊
@SpringBootApplication, enables Spring Boot application and it takes an attribute, “scanBasePackages” with a list of package names in which Spring framework has to search for annotated Spring components and instantiate them. ➋ The
@EnableJpaRepositories is similar to @SpringBootApplication. It enables the Java Persistence (JPA), that is required to build the persistence layer. ➌ The third annotation is the
@EnableTransactionManagement which enables annotation-based Java transaction management (JTA). And the last annotation, ➍ @PropertySource defines the name of the configuration file in the classpath:
Example - 5 : Application class.
With the “dataSource” bean configuration ➎ a data source is configured, that is how the application does reach the database ➏. Spring makes the data source class-wide available. ➐ jpaProperties is a helper method which provides required properties for JPA. ➑ “entityManagerFactory” method is a Java-based bean configuration, that is employed to instantiate EntityManagerFactory instance, that is the gateway to the persistence context where the entities and their life-cycles are managed. Finally, ➒ is the main method to start the Spring Boot application.
The first component we create is the Authorization Server. I will put all application components into a single Spring Boot project for the sake of ease. But, in a real-life application, you probably need to consider to separate the applications, the authorization and resource server. But, before we begin with Authorization and Resource Server, first, we will set up web security. Fortunately, Spring provides configurer adapter classes that need to be inherited while implementing
➊ Please note that, we inject userDetailsService, the service component, that is used to access database to read user data. ➋ The userDetailsServer reference is passed to the Authentication Manager so that it is able to know how to access user database. ➌ We create an authentication manager bean. With ➍, we configure web security and its boundaries. By default, the security rules apply to all requests, but we can also define matchers to tell the framework on which resources what kind of rules are going to be effective in runtime e.g in ➎ and ➏ we permit all request targeting /login while requiring to have the role “USER”.
As we saw earlier, authorization server is the component where tokens are issued to the clients. Implementing an Authorization Server with Spring, is simply to add another Bean configuration into the project. The bean configuration for the authorization server extends AuthorizationServer- ConfigurerAdapter and is annotated with @EnableAuthorizationServer which is necessary to enable the authorization server component. There are two other Spring components get injected via @Autowired, the authenticationManager and the userDetailsService. The latter is the repository and DAO indicating how Spring framework accesses the user data in database and the former is authenticationManager.
The bean configuration for the authorization server extends an adapter class, “AuthorizationServer- ConfigurerAdapter” and it is annotated with @EnableAuthorizationServer which enables the authorization server throughout the application. There are two other Spring components which get injected by @Autowired, the authenticationManager and the userDetailsService. The latter is the instance to the repository and DAO which helps us in accessing the user data and the former is authenticationManager.
Let us stick with the AuthenticationManager a bit, first, the interface with the authenticate() method. The implementation of AuthenticationManager, the ProviderManager is responsible to authenticate the request by iterating over authentication providers. It is technically hooked up to the security servlet filters and does process requests before they reach any REST resources. If AuthenticationManager is successful, it returns a non-null response that indicates that the request is authorized. If none of the providers returns a non-null response, then the request will end up with an AuthorizationException.
In “AuthorizationServerConfiguration”, we define three overloaded configure() methods, ➊ taking a AuthorizationServerSecurityConfigurer as parameter. It configures security rules for Authorization Server, for instance, permit all requests to the endpoint through which access tokens are issued while requiring authorized access on the token endpoint. In the second configure method, ➋ we set up the Client Details Service - just like user details service, it is responsible for providing information about the client. We use in our example an in-memory service with a single static client ➌ “ryos” and its secret, ➍ with read and write scopes and grant types ➎ “password” and “refresh_token”. With ➏, we hand the token store, authentication manager, and the user details service over to the authentication server endpoint configurer. ➐ As token store, we use also an in-memory store provided by the framework.
It is now time to set up the resource server. In resource server configuration, we do not have that much. The configuration bean is annotated with @EnableResourceServer while extending ResourceServer- ConfigurerAdapter class, provided by the framework. The bean configuration overrides the configure method and defines the resource id.
In the overloaded configure()- method which takes the HttpSecurity parameter, HttpSecurity instance is configured while requiring the requests having authorized on matching resource “/version” and having authenticated.
The REST Resource
The API’s REST resource which is handling the review requests looks like as follows:
➊ The REST resource handler accepts GET requests targeting to
/reviews API endpoint. The response object that is returned from the BookReviewService ➋ of which instance is injected to the resource by Spring’s IoC container. ➌ The resource handler method showReview does, in addition, declare an argument,
Principal which contains the security context, about that we talk later and an HTTP query parameter “isbn” ISBN number of the book.
We can test the authorization server by making requests, for instance, with cURL and ask for an access token as well as refresh token:
Once we acquired the access token, we now are able to access protected resources. But first, let me try to call the REST resource with the access token:
As you can see, we may not access the protected resource without providing an access token. In the next example request, we provide the access token in the Authorization header as bearer: