This sample code shows a Java Spring Web application accessing Webfleet Solutions APIs using OAuth 2.0 authorization code flow.
This application features:
- Authenticate an OAuth 2.0 client
- Consume a Webfleet Solutions API
- Refresh access token
- Web interface
- Form based authentication
Note
This project is not production ready, lacks proper security mechanisms. Secure channel communication, safe data storage and others are out of the scope for this project.
Flow starts on /service
endpoint in ServiceController,
simply redirecting the user-agent (browser) to Webfleet Solutions authentication server authorization endpoint indicating the flow to follow (response_type=code).
Authorization request example triggering user's authentication:
Parameter | Mandatory | Description |
---|---|---|
response_type | ✔️ | OAuth 2.0 grant flow, we are using code to signal we are triggering an authorization code flow |
client_id | ✔️ | Client identifier provided by Webfleet solutions during registration process |
redirect_uri | ✔️ | Callback uri user-agent should be redirected to after successful authentication |
state | ✔️ | Random value used for validation during callback request |
scopes | optional | Scopes parameter with requesting scopes. See Webfleet Solutions API documentation to learn about required scopes |
GET /auth/realms/webfleet/protocol/openid-connect/auth?scope=<YOUR_SCOPES>&redirect_uri=<YOUR_REDIRECT_URI>&client_id=<YOUR_CLIENT_ID>&response_type=code&state={random} HTTP/1.1
Host: https://login.webfleet.com
Replacing placeholders with the appropriate values the request will trigger user authentication on Webfleet Solutions authentication server requesting the user to introduce the credentials.
In case the provided client_id is not correct, the following screen will appear.
Once the user has authenticated its user-agent is redirected to the uri informed in the redirect_uri parameter. In this sample it points to the CallbackController. Modify webfleet.redirecturi setting in application.yml to match your setup.
Webfleet Solutions authentication server will add a an authorization code as query parameter (code) to the callback uri which can be used to obtain access and refresh tokens.
Note
For security reasons authorization code can only be exchanged once and has a validity of 10min. (600 seconds).
To obtain access and refresh tokens we need to exchange the previously received authorization code after a successful user authorization,
this is shown in the /callback
endpoint implemented in CallbackController.
Authorization code exchange request example:
POST /auth/realms/webfleet/protocol/openid-connect/token HTTP/1.1
Host: https://login.webfleet.com
Content-type: application/x-www-form-urlencoded
Accept: application/json
grant_type=authorization_code&client_id=<YOUR_CLIENT_ID>&client_secret=<YOUR_CLIENT_SECRET>&code={code}&redirect_uri=<YOUR_REDIRECT_URI>
Parameter | Mandatory | Description |
---|---|---|
grant_type | ✔️ | OAuth 2.0 grant flow, we are using authorization_code following authorization code flow |
client_id | ✔️ | Client identifier provided by Webfleet solutions during registration process |
client_secret | ✔️ | Client secret provided by Webfleet solutions during registration process |
code | ✔️ | Authorization code obtained during the authentication process |
redirect_uri | ✔️ | Redirect uri associated to the OAuth client provided during the partner registration process |
scope | ✔️ | List of OAuth 2.0 scopes. The use of the scope "offline_access" is needed to obtain offline tokens |
A successful response from the previous request would be something like:
{
"access_token": "string",
"token_type": "bearer",
"refresh_token": "string",
"expires_in": 0,
"scope": "string",
"services": [],
"jti": "string"
}
- access_token : Used to authorize Webfleet Solutions APIs requests
- refresh_token : Used to obtain a new access_token, MUST be stored safely (recommendation: use symmetric encryption to persist it)
- token_type : How this token is used to authenticate requests. Default 'bearer' meaning must be informed using a bearer authorization header.
- expires_in : Access token expiration time duration in seconds
- scope : Scopes granted to the provided access token
- services : Custom property carrying Webfleet Solutions information
- jti : Access token identifier
Note
Beware access and refresh tokens have their own expiration times.
Typically access tokens are short lived being valid for just a few hours, while refresh tokens are long lived and may have an expiration time of months or years.
Refresh tokens MUST be stored in a secure place. This token can be exchanged for a new access token so effectively granting access to user's data. In case of leak, please notify Webfleet Solutions as soon as possible.
To request a protected resource from a Webfleet Solutions API we need a valid not expired access token informed using an Authorization header using a Bearer token (RFC6750). In this example we are requesting the available logging user's info because is a common request that all our clients should have. However, you can try any request to any API you have access to.
GET /auth/realms/webfleet/protocol/openid-connect/userinfo HTTP/1.1
Host: https://login.webfleet.com
...
Authorization: Bearer <ACCESS_TOKEN>
...
This token may have expired or may expire during the process, especially if this process spans over multiple requests to Webfleet Solutions APIs, in such cases use a refresh token previously stored to obtain a new access token. This process is covered by the next section.
Once an access token has expired, a refresh token can be used to request a new access token without requesting the resource owner to authenticate again.
This is shown in /refresh
endpoint in RefreshTokenController
This is similar to previous step in where an access token is obtained using an authorization code, remark there is no authorization code in this flow, instead we use a refresh token which we can use on Webfleet Solutions authentication server with a different flow, OAuth 2.0 Refresh token grant flow, to issue a new access token.
POST /auth/realms/webfleet/protocol/openid-connect/token HTTP/1.1
Host: https://login.webfleet.com
Content-type: application/x-www-form-urlencoded
Accept: application/json
grant_type=refresh_token&client_id=<YOUR_CLIENT_ID>&refresh_token={refresh_token}
Parameter | Mandatory | Description |
---|---|---|
grant_type | ✔️ | OAuth 2.0 grant flow, we are using refresh_token following authorization code flow to request a new access token |
client_id | ✔️ | Client identifier provided by Webfleet solutions during registration process |
refresh_token | ✔️ | Refresh token representing resource owner's previous authorization for issuing access tokens on its behalf |
A successful response will be something like:
{
"access_token": "string",
"token_type": "bearer",
"refresh_token": "string",
"expires_in": 0,
"scope": "string",
"services": [],
"jti": "string"
}
- access_token : Used to authorize Webfleet Solutions APIs requests
- refresh_token : Used to obtain a new access_token, MUST be stored safely (recommendation: use symmetric encryption to persist it)
- token_type : How this token is used to authenticate requests. Default 'bearer' meaning must be informed using a Bearer Authorization header.
- expires_in : Access token expiration time duration in seconds
- scope : Scopes granted to the provided access token
- services : Custom property carrying Webfleet Solutions information
- jti : Access token identifier
Note
Refreshing an access token returns a new refresh_token. New refresh token must replace previously stored ones.
Revocation of refresh tokens is implemented following OAuth 2.0 Token revocation (RFC7009). Given WFS authentication server uses JSON Web Token specification (RFC7519) to issue signed self-contained tokens, only refresh tokens can be centrally revoked, access tokens stay valid until they have expired and cannot be revoked.
OAuth clients may revoke any refresh token issued to them, thus not requiring customer consent to revoke access to a customer granted refresh token.
Revoking a refresh token requires the following parameters in a form encoded request.
Parameter | Mandatory | Description |
---|---|---|
token | ✔️ | Refresh token to be revoked |
Example using Basic authentication to inform client credentials
POST /auth/realms/webfleet/protocol/openid-connect/revoke
Host: https://login.webfleet.com
Authorization: Basic PHlvdXJfY2xpZW50X2lkPjo8eW91cl9jbGllbnRfc2VjcmV0PiA=
Content-Type: application/x-www-form-urlencoded
token=eyJhbGciOiJ...
A successful response will always return 200 OK HTTP status meaning the refresh token was revoked.
As defined in RFC7009 revocation response will return 200 OK HTTP status even for invalid tokens, revoking an invalid token has no purpose since its invalidation is already achieved.
You must have contacted Webfleet Solutions before trying this application, follow the registration process which will provide you with a pair of OAuth client credentials. To fully run the example application you will also need a valid Webfleet Solutions subscription and user with the appropriate rights.
Available application credentials
The application is secured using simple form based authentication. Find below the available credentials.
- username: admin
- password: password
Application users are hard coded and stored in memory, modify WebSecurityConfig to fit your needs.
Multiple settings can be tuned in application.yml. By default the application starts a jetty instance listening on port 9080, this can be modified changing server.port setting in application.yml.
The application uses Spring Boot and gradle, to run the application using gradle
./gradlew bootRun
Alternatively you may run it as a java application whose main class is com.webfleet.oauth.Application
Note
WFS_CLIENT_ID and WFS_CLIENT_SECRET environment variables must be defined before executing the application.
A Docker descriptor is provided to easily run the application without installing any other dependencies than Docker.
Replace your client credentials with the placeholders in the below command to build and run the container.
docker build -t oauth-java-example .
&& docker run
-p 9080:9080
--env WFS_CLIENT_ID=<YOUR_CLIENT_ID>
--env WFS_CLIENT_SECRET=<YOUR_CLIENT_SECRET>
--name oauth-java-example
--rm
oauth-java-example
Note
Depending on your operative system you may experiment trouble with the gradlew file end of line. Execute ./gradlew wrapper command in your terminal before executing the docker build to set the end of line format according to your operative system.
This code is licensed under MIT License.