Spring Security: Develop a REST-Service Secured With OAuth2, Bitbucket and JWT
How to use Spring Security to develop a REST-service secured with OAuth2, Bitbucket authorization server and JWT
We are going to develop a small REST-service secured using OAuth2 protocol with Bitbucket as an authorization server. At first it may seem unusual to use a third-party authorization server, but imagine that you are developing a CI (Continuous Integration) server and would like to have access to the user’s resources (e.g projects, repositories) in a version control system. For example, the same approach is used in and .
Theory
Let’s recall some theories before we begin.
Authentication is the process of recognizing a user’s identity. It is often done by asking for a set of credentials, such as username and password. Once verified, the client gets information about the identity and access of the user.
Authorization is the process of granting or denying access to a network resource which allows the user access to various resources based on the user’s identity.
There are two types of authorization: cookie-based and token-based. Let’s see how each type of authorization works in detail.
Cookie-based authorization is stateful, which means the server has to manage a session to be able to authorize user requests. Cookie-based authorization works like this:
- A user is logged in by providing credentials.
- A server creates HTTP-session and associates it with the user. It also creates a cookie named which stores a session identifier.
- The cookie is passed to the client and stored there too.
- The cookie is attached to each subsequent request to the server.
- The server finds a session using the cookie and checks if a user has access to the requested resource.
- When a user logs out from the application a session is deleted from the server.
Because one of the REST architectural constraints is statelessness, a cookie-based authorization is not an option for our example. Let’s see how a token-based authorization is different.
Token-based authorization is stateless, which means the server doesn’t have to store any session information to authorize client requests. Token-based authorization works like this:
- A user is logged in by providing credentials.
- A server issues an access token signed with a secret key. The token is then returned to the client. The token contains a user identifier as well as user’s roles.
- The token is stored in the client and is passed with each subsequent request to the server. Usually the token is passed to the server in
Authorization
HTTP header. - The server verifies the signature, extracts user id, roles and checks if the user has permissions to perform the call.
- When a user logs out, it is enough to just delete a token from the client without even contacting the server.
JSON Web Token (JWT) is a popular token format at the moment. It contains three comma-separated blocks of data: a header, a payload, and a signature. The first two blocks are base64-encoded JSON documents. The payload can consist of some reserved key/value pairs like iss
, iat
, exp
as well as of custom user-defined ones. Symmetric and asymmetric algorithms can be used to sign the token.
We are going to use the OAuth2 protocol to authorize our users so let’s take a glance at it. OAuth2 is a protocol that allows a user to grant a third-party application access to the user’s protected resources, without revealing their credentials. OAuth2 protocol defines 4 authorization flows: authorization code, implicit, resource owner password credentials and client credentials. We are going to use authorization code flow type. Authorization code flow type consists of two subsequent requests:
- authorization request;
- access token request.
Authorization request is used to authenticate a user and to request authorization for our application to access a user’s resources on the resources server. It is just a GET request with the following parameters:
- response_type — must be set to
code
; - client_id — a value obtained from authorization server;
- redirect_uri — a callback URI;
- scope — optional parameter, specifying access level;
- state — random string used for response verification.
Request example:
GET http://server.example.com/authorize?response_type=code&client_id=CLIENT_ID&state=xyz&redirect_uri=REDIRECT_URI
In case of successful authorization a user agent is redirected to the callback URL provided in the request. A code
parameter contains authorization code and a state
parameter contains a value provided in the request.
Response call example:
GET http://client.example.com/cb?code=AUTH_CODE_HERE&state=xyz
Access token request is used to exchange authorization code to access token. It is a with the following parameters:
- grant_type — must be
authorization_code
; - code — authorization code obtained on the previous step;
- redirect_uri — must be the same as on the previous step;
- client_id — a value obtained from authorization server;
- client_secret — a value obtained from authorization server.
Request example:
POST http://server.example.com/token grant_type=authorization_code&
code=AUTH_CODE&
redirect_uri=REDIRECT_URI&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
A response contains an access token and its lifetime as well as a refresh token:
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA"
}
Good news is that all this flow is implemented by Spring Security and we don’t have to worry about implementing it.
Implementation
We are going to develop a REST-service exposing the following API:
- GET /auth/login — start authentication process;
- POST /auth/token — request a new pair of access/refresh tokens;
- GET /api/repositories — request a list of repositories of the current users.
Please note that because the application is made up of three components, not only client requests to the server but also server requests to Bitbucket should be authorized. For the simplicity of this example, we are not going to setup role-based authorization. Every authenticated client is permitted to access /api/repositories
the endpoint. The server is permitted to make any requests to Bitbucket which were granted during OAuth2 client registration. We will use Spring Boot version 2.2.2.RELEASE and version 5.2.1.RELEASE.
Register OAuth2 consumer
First of all we have to register our application on Bitbucket as OAuth2 consumer. To do this open your user account settings and add a consumer as shown below:
Fill in the name and callback URL and then grant the following permissions:
You will be provided with 2 properties, store them in application.properties
file.
client_id=ZJsdANfWkJ7jcktw2x
client_secret=28uUrJ9m43svbkcnXVNj8qeBjFtd8jaD
Authentication entry point
When a secured resource is accessed in a web-application and there is no authenticated user in the security context the user is redirected to the login page. This behavior is not acceptable for a REST-application though. It would be better to return HTTP 401 (UNAUTHORIZED) error in this case. This is what our RestAuthenticationEntryPoint
class does.
Create login and refrsh tokens endpoints
We are going to use OAuth2 Authorization Code flow to authenticate a user, however we have overridden a default AuthenticationEntryPoint on the previous step that’s why we need an explicit way to trigger authentication. We are going to redirect a user to Bitbucket authentication page in the /auth/login
endpoint handler. This endpoint requires a callback URL to be passed in a query parameter which will be used to provide a client with the JWT after successful authentication.
For security purposes access token’s lifetime is usually quite limited. In this case if it is stolen, a third party will not be able to use it for a long time. Not to make a user to re-login each time the token is expired a refresh token is used to obtain a new access token. A refresh token is issued by the server together with an access token and it has a longer lifetime. The recommended way to store and transfer refresh tokens is using HttpOnly cookies.
Authentication success handler
AuthenticationSuccessHandler
is called after successful authentication. It is a good place to generate a pair of access/refresh tokens and to redirect a user to the callback URL provided on the login step. Access token is returned using a query parameter and refresh token is returned using HttpOnly cookie.
Token authentication filter
The purpose of this filter is to extract the access token from the Authorization header and to initialize a security context.
Authorization request repository
Spring Security uses an instance of AuthorizationRequestRepository
to keep OAuth2AuthorizationRequest
objects during authentication. The default implementation is HttpSessionOAuth2AuthorizationRequestRepository
which uses HttpSession
as a storage. This implementation doesn’t suit our requirements as our service is stateless. That’s why we have to implement our own request repository which will use cookies as a storage.
Configure Spring Security
Finally, let’s combine all parts together and create a configuration class.
Repositories endpoint
Now we can use the benefits of our integration with Bitbucket. Let’s use Bitbucket repositories API to fetch a list of user’s repositories.
Testing
We need a small HTTP-server to emulate a real client. It will be used as a destination to return a token to.
At first, let’s try to call repositories endpoint before authentication and check that we are getting a 401 error.
Then we perform authentication. To do this we need to start our server and open URL in a browser.
After successful authentication, a client will receive a token and will call the repositories endpoint one more time. After that, a new token is requested and then used to call repository endpoint.
Source code
Source code of this example can be found on .
Resources
P.S. This example works over HTTP connection for simplicity. But note that our tokens are not encrypted and in general it is better to switch to the secured connection (HTTPS).