Sunday, June 21, 2020

OAuth2 and JWT Tokens Part 1

In this blog post we look at how do we make our spring server become an OAuth2 Authorization server and start producing JWT tokens.

The first step that we take is disable all the filters that we had added earlier. We don't want to authorize using old style REST calls.  

Now, the first step is to enable our authorization server. We create a new java configuration file that extends AuthorizationServerConfigurerAdapter.

In the configuration , we autowire AuthenticationManager, DataSource and a UserDetailsService. There are two types of auth tokens in OAuth2. The first one are the usual tokens that are requested by users by providing their username and password. The second set of tokens are not tied to individual users, these are called client tokens and could be used for services talking to each other.

We configure ClientDetailsService to use Jdbc client service. We add a password encoder in the configuration and provide a data source that would be used to store persistent data. In our example, we are using SCryptPasswordEncoder. Here we are using spring provided Jdbc client service but one could implement ClientDetailsService and ClientRegistrationService interfaces and provide their own custom implementation for client service.

The next steps is to enable web security and provide an authentication manager bean for performing authentication.

We have seen earlier that we used a UserDetailsService to configure the authorization server. We are not going to use the spring provided service but write our own. 

In the UserDetailsService, we need to provide our own implementation for a method loadByUsername. In this method, we basically load the user entity from our database and create an object of type UserDetails and return it. The object also requires a list of GrantedAuthority

UserDetails is an interface provided in springframework, we create our own class that implements the interface.

Similar to UserDetails, GrantedAuthority is also an interface provided by springframework, we implement that interface to provide our concrete implementation of GrantedAuthority. For a very simple understanding, GrantedAuthority is like a user role. We can use this role later to provide access control on endpoints.

These changes will make the server ready for OAuth2 service. We still have a testing nightmare. Because now all our endpoints are behind this authentication filter, there is no way for us to create new clients and users. We could directly insert values in database, but we still have to worry about how to encrypt the password before inserting into the data. To get out of this situation, I add two endpoints, one for handling clients and another for handling users. These endpoints need to be configured so that they don't go through the authentication service. These need to be removed before the service goes into production.

We define a pojo that mimics the OauthClientDetails schema in the database. It looks like below.

Now we add a repository for handling OauthClientDetails table.

We also add a service layer for Client handling.


We already had a UserRepository, we change it to encrypt the password before use store the password.

We also add a service layer for User.

Now that we have all the layers required, we add the endpoint for Client.


The next thing to modify is the endpoint layer for user.

Now we are ready with our code for create new clients and users. We still have the small issue because if we hit these endpoints, we will get unauthorized error. So we need to put these in the exception list. For this we go to our SecurityConfiguration that we had defined in one of the earlier tutorials.

Look at the method public void configure(WebSecurity web) throws Exception.  We have added following two lines to ignore evaluation of authentication filters for two families of URL.
        web.ignoring().antMatchers("/oauth/client/**");
        web.ignoring().antMatchers("/user/**");
Of course this is very dangerous and we have added it only for testing purpose. In a future tutorial we will have a more elegant solution for this.

Now we are ready. We can test the service with following curl or equivalent command.

curl -X POST \
  http://localhost:8081/oauth/token \
  -H 'authorization: Basic YW5kcm9pZC1jbGllbnQ6YW5kcm9pZC1zZWNyZXQ=' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -H 'postman-token: 3a349bc0-1230-adbe-4b79-9b938728a101' \
  -d 'grant_type=password&password=mypassword&username=myuser&client_id=my-client&client_secret=my-secret'

This is the response that we get

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI3MjI5NTgsInVzZXJfbmFtZSI6InZhdmFzdGhpIiwianRpIjoiOGExZDYxN2ItZDU4OC00Nzc5LThlOTQtYTBiNWZkYzcxOTg2IiwiY2xpZW50X2lkIjoiYW5kcm9pZC1jbGllbnQiLCJzY29wZSI6WyJjb2RlIl19.n7hCnBdnjMC8vuCsHxkOznfd06iJctGNeypx2fXWla4",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI3MjMwNTgsInVzZXJfbmFtZSI6InZhdmFzdGhpIiwianRpIjoiNGU4ODg3OTYtZWZiZS00ZDc5LTg1YmMtN2EzNzFhM2I4Yzg0IiwiY2xpZW50X2lkIjoiYW5kcm9pZC1jbGllbnQiLCJzY29wZSI6WyJjb2RlIl0sImF0aSI6IjhhMWQ2MTdiLWQ1ODgtNDc3OS04ZTk0LWEwYjVmZGM3MTk4NiJ9.xuElTWYSLscgdcqj0t-4t6prJbVOfHVqM331UUjfPBQ",
    "expires_in": 99,
    "scope": "code",
    "jti": "8a1d617b-d588-4779-8e94-a0b5fdc71986"
}
We can get the refresh token by calling the same endpoint with grant_type refresh_token.

curl -X POST \
>   http://localhost:8081/oauth/token \
>   -H 'authorization: Basic YW5kcm9pZC1jbGllbnQ6YW5kcm9pZC1zZWNyZXQ=' \
>   -H 'cache-control: no-cache' \
>   -H 'content-type: application/x-www-form-urlencoded' \
>   -H 'postman-token: 0adf261f-ed1e-85b3-67a5-21430fee8b38' \
>   -d 'grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI3MjMxOTAsInVzZXJfbmFtZSI6InZhdmFzdGhpIiwianRpIjoiNGMwNjczOTQtZjllNS00NDVjLTg5MzItMmRiMDM4N2U2ZjIxIiwiY2xpZW50X2lkIjoiYW5kcm9pZC1jbGllbnQiLCJzY29wZSI6WyJjb2RlIl0sImF0aSI6ImE1ZmY5MjhiLWQ1YjEtNDQ5Yy04N2I4LTU3ODgwYzY1NjM3NiJ9.RS2K7N5XCHQNov02WNu1QK1AqOvgc7MuNzeCydt7Ajs'
We get following response.
{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI3MjMxNzQsInVzZXJfbmFtZSI6InZhdmFzdGhpIiwianRpIjoiY2E1OWU3NzktZDRkZS00NDU1LWFlNjItNDQ2NTAxYzUxZjhmIiwiY2xpZW50X2lkIjoiYW5kcm9pZC1jbGllbnQiLCJzY29wZSI6WyJjb2RlIl19.WgVgpBWuDmdJUk3UG8MTKOBsXn5zbEGO8gyqg2akN8o",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI3MjMxOTAsInVzZXJfbmFtZSI6InZhdmFzdGhpIiwianRpIjoiNGMwNjczOTQtZjllNS00NDVjLTg5MzItMmRiMDM4N2U2ZjIxIiwiY2xpZW50X2lkIjoiYW5kcm9pZC1jbGllbnQiLCJzY29wZSI6WyJjb2RlIl0sImF0aSI6ImNhNTllNzc5LWQ0ZGUtNDQ1NS1hZTYyLTQ0NjUwMWM1MWY4ZiJ9.3zDqHLSwVLq_Dg8r4Ppf5tfvzxwT_7FEwdTV67l3VYQ",
    "expires_in": 99,
    "scope": "code",
    "jti": "ca59e779-d4de-4455-ae62-446501c51f8f"
}
We can verify a token by accessing oauth/check_token endpoint and providing the token.

curl --request POST \
>   --url http://localhost:8081/oauth/check_token \
>   --header 'authorization: Basic YW5kcm9pZC1jbGllbnQ6YW5kcm9pZC1zZWNyZXQ=' \
>   --header 'cache-control: no-cache' \
>   --header 'content-type: application/x-www-form-urlencoded' \
>   --header 'postman-token: ef035580-4cba-a7a1-deaa-dcf5f59e41fc' \
>   --data token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI3MjM2MTEsInVzZXJfbmFtZSI6InZhdmFzdGhpIiwianRpIjoiODZhZjZhYmUtZjA4YS00YzIzLThkYzYtYjY2ZmM3ZWQ0YWRiIiwiY2xpZW50X2lkIjoiYW5kcm9pZC1jbGllbnQiLCJzY29wZSI6WyJjb2RlIl19.Kn284yuxxHSxVtEc3D8H5YjVjPi0yN6oX1hDwEx5bOo
We get the following response.
{
    "client_id": "android-client",
    "exp": 1592723611,
    "jti": "86af6abe-f08a-4c23-8dc6-b66fc7ed4adb",
    "scope": [
        "code"
    ],
    "user_name": "myuser"
}
This is all about enabling JWT OAuth tokens with any spring server. We will continue this tutorial in next part with some more important details. The complete code for the working server is here.

No comments:

Post a Comment