4 ApiFest OAuth 2.0 Server
ApiFest OAuth 2.0 Server implements OAuth 2.0 protocol as per the RFC 6749. It is used to secure your API using the OAuth 2.0 protocol, enabling the usage of access tokens in the ApiFest Mapping Server. The ApiFest OAuth 2.0 Server currenlty supports MongoDB, Redis, Cassandra and Hazelcast as a storage.4.1 Start ApiFest OAuth 2.0 Server
In order to start the ApiFest OAuth 2.0 Server, you should configure several properties. Here is how the configuration file looks like:
oauth20.host=
oauth20.port=
oauth20.database=
db_uri=
redis.sentinels=
redis.master=
apifest-oauth20.nodes=
custom.classes.jar=
user.authenticate.class=
custom.grant_type=
custom.grant_type.class=
hazelcast.password=
oauth20.port=
oauth20.database=
db_uri=
redis.sentinels=
redis.master=
apifest-oauth20.nodes=
custom.classes.jar=
user.authenticate.class=
custom.grant_type=
custom.grant_type.class=
hazelcast.password=
In the following table you can find the description of each property, whether it's mandatory and an example value.
property | description | required | example |
---|---|---|---|
oauth20.host | The host on which the ApiFest OAuth 2.0 Server will run. | false, default value localhost | oauth20.host=127.0.0.1 |
oauth20.port | The port on which the ApiFest OAuth 2.0 Server will run. | false, default value 8080 | oauth20.port=8080 |
oauth20.database | The type of the database used by the ApiFest OAuth 2.0 Server - supported values are: redis, mongodb, cassandra and hazelcast | false, if not set then Hazelcast will be used as a storage | oauth20.database=mongodb |
db_uri | The URI to the MongoDB. | true only if oauth20.database is mongodb | db_uri=mongodb://127.0.0.1:27017 |
redis.sentinels | Redis sentinels comma separated list. | true only if oauth20.database is redis | redis.sentinels=10.32.23.157:26379 |
redis.master | Redis master | true only if oauth20.database is redis | redis.master=mymaster |
apifest-oauth20.nodes | A comma-separated list of all ApiFest OAuth 2.0 Server nodes defined by host only. Used to create a Hazelcast cluster in case Hazelcast is used as a storage. | false, default value=localhost | apifest-oauth20.nodes=10.32.23.157,10.32.23.158 |
custom.classes.jar | The path to the jar with your custom classes - for instance, the class that implements custom user authentication. | false* | custom.classes.jar=/home/apifest-oauth/custom-classes.jar |
user.authenticate.class | The fully qualified class that authenticates users. Details about custom user authentication in chapter 4.2 | false* | user.authenticate.class=com.apifest.example.UserAuthentication |
custom.grant_type | A custom grant_type (not supported by RFC6749), if needed for any reasons | false | custom.grant_type=otp |
custom.grant_type.class | The fully qualified class that authenticates users when custom grant_type is used. | false | custom.grant_type.class=com.apifest.example.OTPHandler |
hazelcast.password | Hazelcast password used to secure the Hazelcast nodes (in case Hazelcast is used as a storage).If not defined the default Hazelcast password will be used. | false, default Hazelcast password will be used - dev-pass | hazelcast.password=securepass |
*If no custom.classes.jar and user.authenticate.class is set then user authentication will be successful no matter the username and the password passed, userId returned is 123456. So you are strongly recommended to implement custom user authentication - see more in the next chapter 4.2.
4.2 Create custom authentication
The ApiFest OAuth 2.0 Server is responsible for user authentication and as different applications use different user authentication methods and flows - for instance, some applications may require additional information in order to login the user, the ApiFest OAuth 2.0 Server enables custom authentication. That means you can implement your own user authentication - a class that implements com.apifest.oauth20.api.IUserAuthentication interface. The fully qualified class should be set as user.authenticate.class in the ApiFest OAuth 2.0 Server properties file. The class should be bundled in a jar that should be set as custom.classes.jar in the properties file.Let's see an example. Let's say we have a user authentication service in the backend application available on http://127.0.0.1:5000/users/authenticate. The service expects a POST request with username and password in JSON format and if the authentication is successful returns JSON response that contains user_id. Then the class that will be used for user authentication will call that service. Note, that the implementation is a dummy one and it is used only for demo purposes.
public class UserAuthentication implements IUserAuthentication {
@Override
public UserDetails authenticate(String username, String password, HttpRequest authRequest) throws AuthenticationException {
UserDetails userDetails = null;
HttpPost post = new HttpPost("http://127.0.0.1:5000/users/authenticate");
JsonObject json = new JsonObject();
json.addProperty("username", username);
json.addProperty("password", password);
try {
post.setEntity(new StringEntity(json.toString(), "UTF-8"));
post.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
HttpClient client = HttpClientBuilder.create().build();
HttpResponse httpResponse = client.execute(post);
if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new AuthenticationException("invalid username/password");
} else {
userDetails = createUserDetails(httpResponse);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return userDetails;
}
private UserDetails createUserDetails(HttpResponse response) {
UserDetails details = null;
InputStream input = null;
try {
input = response.getEntity().getContent();
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> json = mapper.readValue(input, Map.class);
details = new UserDetails(json.get("user_id").toString(), null);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return details;
}
}
@Override
public UserDetails authenticate(String username, String password, HttpRequest authRequest) throws AuthenticationException {
UserDetails userDetails = null;
HttpPost post = new HttpPost("http://127.0.0.1:5000/users/authenticate");
JsonObject json = new JsonObject();
json.addProperty("username", username);
json.addProperty("password", password);
try {
post.setEntity(new StringEntity(json.toString(), "UTF-8"));
post.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
HttpClient client = HttpClientBuilder.create().build();
HttpResponse httpResponse = client.execute(post);
if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new AuthenticationException("invalid username/password");
} else {
userDetails = createUserDetails(httpResponse);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return userDetails;
}
private UserDetails createUserDetails(HttpResponse response) {
UserDetails details = null;
InputStream input = null;
try {
input = response.getEntity().getContent();
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> json = mapper.readValue(input, Map.class);
details = new UserDetails(json.get("user_id").toString(), null);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return details;
}
}
As you can see the method that should implemented in the custom user authentication is authenticate(String username, String password, HttpRequest authRequest). It accepts as parameters username, password and the authentication request posted to the ApiFest OAuth 2.0 Server. For instance, if some headers are required for the user authentication they could be extracted from the authRequest. The authenticate method returns UserDetails object that contains the id of the user (String userId) and some details (Map<String, String> details) related to the user that can be stored along with the access token that will be issued.
In order to use that class for user authentication, it should be bundled in a jar. Let's say in our example the jar is located in /home/apifest/authentication.jar. Then the following properties should look like:
user.authenticate.class=com.apifest.example.UserAuthentication
custom.classes.jar=/home/apifest/authentication.jar
Then every time a user access token is issued by the ApiFest OAuth 2.0 Server the UserAuthentication class will be invoked.
4.3 Register a scope
As described in chapter 3.10, a scope in terms of OAuth 2.0 defines the resources access tokens with the given scope can access. The definition of a scope in ApiFest is done in mappings configuration file on the endpoint level, i.e. each endpoint contains the scope/s that it is/are allowed for.For instance:
<endpoint external="/v0.1/countries" internal="/common/countries" method="GET" scope="basic extended" authType="client-app"/>
That means the resource /v0.1/countries will be accessible with access tokens that have basic or extended scope. Now let's see how a scope could be registered in the ApiFest OAuth 2.0 Server and what properties it has. Here is an example request for scope registration:
POST /oauth20/scopes
Content-Type: application/json
{
"scope": "basic",
"description": "basic scope allows ...",
"cc_expires_in": "300",
"pass_expires_in": "120",
"refresh_expires_in": "300"
}
Content-Type: application/json
{
"scope": "basic",
"description": "basic scope allows ...",
"cc_expires_in": "300",
"pass_expires_in": "120",
"refresh_expires_in": "300"
}
The meaning of each JSON field of the request body is described in the following table:
field | description | required |
---|---|---|
scope | The name of the scope. A valid name may contain aplha-numeric, - and _ | true |
description | The description ofthe scope. | true |
cc_expires_in | The expiration time(in seconds) of access tokens with grant_type=client_credentials. | true |
pass_expires_in | The expiration time(in seconds) of access tokens with grant_type=password. | true |
refresh_expires_in | The expiration time(in seconds) of refresh access tokens. Note, refreshtokens are issued only for access tokens with grant_type=password. | false, the default value is the value of pass_expires_in |
All scope fields except scope could be updated later - see chapter 6.3.1.
A scope name cannot contain space because the space is used as a delimiter when several scopes are set to a client application and access tokens.
A scope name cannot contain space because the space is used as a delimiter when several scopes are set to a client application and access tokens.
Here are how the different responses look like:
- Successful response
Status Code: 200 OK
Content-Type: application/json
{"status":"scope successfully stored"}
- If the scope has been already registered, then the following response will be returned
Status Code: 400 Bad Request
Content-Type: application/json
{"status":"scope already exists"}
- If some of the mandatory parameters are missing
Status Code: 400 Bad Request
Content-Type: application/json
{"error":"scope, description, cc_expires_in and pass_expires_in are mandatory"}
- If the scope name is not valid
Status Code: 400 Bad Request
Content-Type: application/json
{"error":"scope name not valid - it may contain aplha-numeric, - and _"}
4.4 Register a client application
In order to register a client application in the ApiFest OAuth 2.0 Server, you should post a request to /oauth20/applications.Here is an example request:
POST /oauth20/applications
Content-Type: application/json
{
"name": "testApp",
"description": "This is a test app",
"scope": "basic",
"redirect_uri": "http://10.46.16.93:8080",
"client_id": "b9db6d84dc98a895035e68f972e30503d3c724c8",
"client_secret": "105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838",
"application_details": {
"division": "IT",
"organization": "MM"
}
}
Content-Type: application/json
{
"name": "testApp",
"description": "This is a test app",
"scope": "basic",
"redirect_uri": "http://10.46.16.93:8080",
"client_id": "b9db6d84dc98a895035e68f972e30503d3c724c8",
"client_secret": "105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838",
"application_details": {
"division": "IT",
"organization": "MM"
}
}
The meaning of each JSON field of the request body is described in the following table:
field | description | required |
---|---|---|
name | The name of the client application. | true |
description | The description of the client application. | false |
scope | The scope/s allowed to the client application. If several scopes are allowed they should be separated by space. | true |
application_details | Custom details about the client application represented as a JSON object | false |
redirect_uri | The redirection URI that will be used in case of authorization code flow. | true, used only for authorization code flow |
client_id | A predefined client_id of the client application. | false |
client_secret | A predefined client_secret of the client application. | false |
Note, client_id and client_secret fields are used for migration purposes (if you have already client applications registered in another system) and it's not recommended to pass them if you register a new client application.
Here are how the different responses look like:
- Successful response
Status Code: 200 OK
Content-Type: application/json
{
"client_id": "b9db6d84dc98a895035e68f972e30503d3c724c8",
"client_secret": "105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838"
}
- If you try to register a client application with predefined client_id and client_secret that have been already registered, then the following response is returned
Status Code: 400 Bad Request
Content-Type: application/json
{"error": "already registered client application"}
- If you try to register a client application with a non-existing scope
Status Code: 400 Bad Request
Content-Type: application/json
{"error": "scope does not exist"}
- If some of the mandatory parameters are missing or invalid
Status Code: 400 Bad Request
Content-Type: application/json
{"error": "name, scope or redirect_uri is missing or invalid"}
By default a registered client application status is 0 - meaning it is not active. In order to issue an access token for that client application, it should be activated first.
Here is an example request for client application activation:
PUT /oauth20/applications/b9db6d84dc98a895035e68f972e30503d3c724c8
Content-Type: application/json
{"status":1}
where b9db6d84dc98a895035e68f972e30503d3c724c8 is the client_id of the client application.Content-Type: application/json
{"status":1}
If the client application status is updated successfully, the following response will be returned:
Status Code: 200 OK
Content-Type: application/json
{"status":"client application updated"}
Content-Type: application/json
{"status":"client application updated"}
If the client application status is not updated, the following response will be returned:
Status Code: 400 Bad Request
Content-Type: application/json
{"error": "cannot update client application"}
Content-Type: application/json
{"error": "cannot update client application"}
4.5 Obtain an access token
As per the RFC-6749 (http://tools.ietf.org/html/rfc6749), the supported grant types in the ApiFest OAuth 2.0 Server are: authorization_code, client_credentials, password and refresh_token. Let's see how to obtain an access token with each of these grant types.4.5.1 Authorization code flow
In this flow, an access token is obtained using an authorization code that is obtained as a first step.Below are example request and response for obtaining an authorization code.
Request:
GET /oauth20/auth-codes?client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&redirect_uri=http://10.46.16.93:8080&response_type=code&scope=basic
Response:
{"redirect_uri": "http://10.46.16.93:8080?code=UXtpWHNWVTdLxQSxfcCOUKuDEZQ%23MXi%3Dqx-BfQiXdwJ-TTeU%3DzBdVtqTSeAWFjDStdTi_Pao%3DhlnytNEwpvTTpigHfQiUYYogccQQrjHWNMBsxdSYudIDlsExausvQlnFzLgODtNlanlo_KKAItavxKallDSzoNVqRZOi%3DcrJ-DzibyMtMMvBZ%23iRbvgDRVG%3DfSTgj%23D"}
Here is a table that describes the query parameters in the authorization code request:
parameter | description | required |
---|---|---|
client_id | The client_id of the client application an authorization code will be issued for. | true |
redirect_uri | The redirection URI to which the user should be redirected along with the authorization code. | true |
response_type | The response_type should be code for authorization code flow. If the response_type is not code, then the following response will be returned: {"error":"unsupported_response_type"} | true |
scope | The scope/s of the access token that will be issued using that authorization code. The scope/s should be allowed for the client application defined by the client_id. | false, if no scope is passed then the client application scope will be used |
The second step to issue an access token is to use the authorization code returned in the response of the step 1.
Here are example request and response:
Request:
POST /oauth20/tokens
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=UXtpWHNWVTdLxQSxfcCOUKuDEZQ%23MXi%3Dqx-BfQiXdwJ-TTeU%3DzBdVtqTSeAWFjDStdTi_Pao%3DhlnytNEwpvTTpigHfQiUYYogccQQrjHWNMBsxdSYudIDlsExausvQlnFzLgODtNlanlo_KKAItavxKallDSzoNVqRZOi%3DcrJ-DzibyMtMMvBZ%23iRbvgDRVG%3DfSTgj%23D&redirect_uri=http://10.46.16.93:8080&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=UXtpWHNWVTdLxQSxfcCOUKuDEZQ%23MXi%3Dqx-BfQiXdwJ-TTeU%3DzBdVtqTSeAWFjDStdTi_Pao%3DhlnytNEwpvTTpigHfQiUYYogccQQrjHWNMBsxdSYudIDlsExausvQlnFzLgODtNlanlo_KKAItavxKallDSzoNVqRZOi%3DcrJ-DzibyMtMMvBZ%23iRbvgDRVG%3DfSTgj%23D&redirect_uri=http://10.46.16.93:8080&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8
Response:
Status Code: 200 OK
Content-Type: application/json
{
"access_token": "857fd034dabea45bd130ef3f4af4d7929118d91974e00f399335794889404349",
"refresh_token": "b8d4a3bcecdb53479cf59bcb724840123d795a7a3e6b7af6ea43d532436ff44d",
"token_type": "Bearer",
"expires_in": "120",
"scope": "basic",
"refresh_expires_in": "300"
}
Content-Type: application/json
{
"access_token": "857fd034dabea45bd130ef3f4af4d7929118d91974e00f399335794889404349",
"refresh_token": "b8d4a3bcecdb53479cf59bcb724840123d795a7a3e6b7af6ea43d532436ff44d",
"token_type": "Bearer",
"expires_in": "120",
"scope": "basic",
"refresh_expires_in": "300"
}
The following table describes the parameters used to obtain an access token:
parameter | description | required |
---|---|---|
grant_type | authorization_code | true |
code | The authorization code obtained as a result of the authorization code request. | true |
redirect_uri | The redirection URI used to obtain the authorization code. Note, the redirect_uri must be the same as the one used to obtained authorization code. | true |
client_id | The client_id of the client application an access token will be issued for. | true |
client_secret | The client_secret the client application an access token will be issued for. | true |
The following table describes the fields in the response:
field | description | example |
---|---|---|
access_token | The access token issued for a given request. The access token could be used within expires_in period. The time unit is seconds. | 857fd034dabea45bd130ef3f4af4d7929118d91974e00f399335794889404349 |
refresh_token | The refresh token issued for a given request. The refresh token could be used within refresh_expires_in period. The time unit is seconds. | b8d4a3bcecdb53479cf59bcb724840123d795a7a3e6b7af6ea43d532436ff44d |
token_type | The type of the access token. | Bearer |
expires_in | The time within the access token is valid. The time unit is seconds. | 120 |
scope | The scope/s of the access token. | basic |
refresh_expires_in | The time within the refresh token is valid. The time unit is seconds. | 300 |
Here are examples for different responses returned by the service.
- Successful response
Status Code: 200 OK
Content-Type: application/json
"access_token": "a5530044c596681e5e142fa2255bffbf78a2adcfbbaeef0d88a6c1a8407a4cb1",
"refresh_token": "a4a6ea9f621391731153c025888130f39acc725e05174f30ec50719cfce39cb7",
"token_type": "Bearer",
"expires_in": "120",
"scope": "basic",
"refresh_expires_in": "300"
} - If redirect_uri does not match the redirect_uri used to obtain the auth code or the auth code is not valid.
Status Code: 400 Bad Request
Content-Type: application/json
{"error": "invalid auth_code"}
4.5.2 Client credentials grant type
Access tokens with grant type client credentials are obtained by posting a request to /oauth20/tokens with grant_type=client_credentials.Here is an example request:
POST /oauth20/tokens
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&client_secret=105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838&scope=basic
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&client_secret=105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838&scope=basic
The following table describes the parameters used to obtain an access token with grant type client_credentials:
parameter | description | required |
---|---|---|
grant_type | client_credentials | true |
client_id | The client_id of the client application an access token will be issued for. | true |
client_secret | The client_secret the client application an access token will be issued for. | true |
scope | The scope/s of the access token that will be issued. The scope/s should be allowed for the client application defined by the client_id. | false, if no scope is passed then the client application scope will be used |
Here is an example response:
Status Code: 200 OK
Content-Type: application/json
{
"access_token": "1182d52650cc5f41418abb5dfec08d98a485818ea027188e6b90bc2a32477e5e",
"token_type": "Bearer",
"expires_in": "300",
"scope": "basic"
}
The following table describes the fields in the response:Content-Type: application/json
{
"access_token": "1182d52650cc5f41418abb5dfec08d98a485818ea027188e6b90bc2a32477e5e",
"token_type": "Bearer",
"expires_in": "300",
"scope": "basic"
}
field | description | example |
---|---|---|
access_token | The access token issued for a given request. The access token could be used within expires_in period. The time unit is seconds. | 1182d52650cc5f41418abb5dfec08d98a485818ea027188e6b90bc2a32477e5e |
token_type | The type of the access token. | Bearer |
expires_in | The time within the access token is valid. The time unit is seconds. | 120 |
scope | The scope/s of the access token. | basic |
Here are examples for different responses returned by the service.
- Successful response
Status Code: 200 OK
Content-Type: application/json
{
"access_token": "1182d52650cc5f41418abb5dfec08d98a485818ea027188e6b90bc2a32477e5e",
"token_type": "Bearer",
"expires_in": "300",
"scope": "basic"
}
- If the scope passed is not allowed for that client application or the scope does not exist
Status Code: 400 Bad Request
Content-Type: application/json
{"status":"scope not valid"}
- If the client_id or client_secret is not valid or the client application status is not active
Status Code: 400 Bad Request
Content-Type: application/json
{"error": "invalid client_id/client_secret"}
4.5.3 Resource owner password credentials grant
Access tokens with grant type client credentials are obtained by posting a request to /oauth20/tokens with grant_type=password.Here is an example request:
POST / oauth20/tokens
Content-Type: application/x-www-form-urlencoded
grant_type=password&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&client_secret=105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838&username=john&password=secret_pass&scope=basic
Content-Type: application/x-www-form-urlencoded
grant_type=password&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&client_secret=105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838&username=john&password=secret_pass&scope=basic
The following table describes the parameters used to obtain an access token with grant type password:
parameter | description | required |
---|---|---|
grant_type | password | true |
client_id | The client_id of the client application an access token will be issued for. | true |
client_secret | The client_secret the client application an access token will be issued for. | true |
username | The username of the user the access token will be used for. | true |
password | The password of the user the access token will be used for. | true |
scope | The scope/s of th eaccess token that will be issued. The scope/s should be allowed for the client application defined by the client_id. | false, if noscope is passed then the client application scope will be used |
Here is an example response:
Status Code: 200 OK
Content-Type: application/json
{
"access_token": "1fa18309dade07a9e6e02ce0a66c0d5f04653309f9a20eec6b4956748d640e91",
"refresh_token": "540bd3671663cdf6fb24eea5b33fcce3aca67d34d3b4074db4339ee58c1f43d6",
"token_type": "Bearer",
"expires_in": "120",
"scope": "basic",
"refresh_expires_in": "300"
}
The following table describes the fields in the response:Content-Type: application/json
{
"access_token": "1fa18309dade07a9e6e02ce0a66c0d5f04653309f9a20eec6b4956748d640e91",
"refresh_token": "540bd3671663cdf6fb24eea5b33fcce3aca67d34d3b4074db4339ee58c1f43d6",
"token_type": "Bearer",
"expires_in": "120",
"scope": "basic",
"refresh_expires_in": "300"
}
field | description | example |
---|---|---|
access_token | The access token issued for a given request. The access token could be used within expires_in period. The time unit is seconds. | 1fa18309dade07a9e6e02ce0a66c0d5f04653309f9a20eec6b4956748d640e91 |
refresh_token | The refresh token issued for a given request. The refresh token could be used within refresh_expires_in period. The time unit is seconds. | 540bd3671663cdf6fb24eea5b33fcce3aca67d34d3b4074db4339ee58c1f43d6 |
token_type | The type of the access token. | Bearer |
expires_in | The time within the access token is valid. The time unit is seconds. | 120 |
scope | The scope/s of the access token. | basic |
refresh_expires_in | The time within the refresh token is valid. The time unit is seconds. | 300 |
Here are examples for different responses returned by the service.
- Successful response
Status Code: 200 OKAs you can see the response contains refresh_token and refresh_expires_in fields. A refresh token could be used (within refresh_expires_in period) to obtain a new access token without using username and password. You can see more about the refresh token grant type in chapter 4.5.4.
Content-Type: application/json
{
"access_token": "1fa18309dade07a9e6e02ce0a66c0d5f04653309f9a20eec6b4956748d640e91",
"refresh_token": "540bd3671663cdf6fb24eea5b33fcce3aca67d34d3b4074db4339ee58c1f43d6",
"token_type": "Bearer",
"expires_in": "120",
"scope": "basic",
"refresh_expires_in": "300"
}
- If the scope passed is not allowed for that client application or the scope does not exist
Status Code: 400 Bad Request
Content-Type: application/json
{"status":"scope not valid"}
- If the client_id or client_secret is not valid or the client application status is not active
Status Code: 400 Bad Request
Content-Type: application/json
{"error": "invalid client_id/client_secret"}
4.5.4 Refresh token grant type
Access tokens with grant type client credentials are obtained by posting a request to /oauth20/tokens with grant_type=refresh_token.Here is an example request:
POST / oauth20/tokens
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&client_secret=105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838&refresh_token=ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6&scope=basic
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&client_secret=105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838&refresh_token=ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6&scope=basic
The following table describes the parameters used to obtain an access token with grant type refresh_token:
parameter | description | required |
---|---|---|
grant_type | refresh_token | true |
client_id | The client_id of the client application an access token will be issued for. | true |
client_secret | The client_secret the client application an access token will be issued for. | true |
refresh_token | The value of refresh_token obtained along with an access token issued. | true |
scope | The scope/s of theaccess token that will be issued. The scope/s should be allowed for the client application defined by the client_id and the scope MUST NOT include any scope not originally granted by the resource owner. | false, if no scope is passed then the client application scope will be used |
Here is an example response:
Status Code: 200 OK
Content-Type: application/json
{
"access_token": "9ae75e11888d962ce1ffff1fa57cdc2a67380b89f2cfcece0a538ce36b08e5e6",
"refresh_token": "ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6",
"token_type": "Bearer",
"expires_in": "120",
"scope": "basic",
"refresh_expires_in": "300"
}
The following table describes the fields in the response:Content-Type: application/json
{
"access_token": "9ae75e11888d962ce1ffff1fa57cdc2a67380b89f2cfcece0a538ce36b08e5e6",
"refresh_token": "ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6",
"token_type": "Bearer",
"expires_in": "120",
"scope": "basic",
"refresh_expires_in": "300"
}
field | description | example |
---|---|---|
access_token | The access token issued for a given request. The access token could be used within expires_in period. The time unit is seconds. | 9ae75e11888d962ce1ffff1fa57cdc2a67380b89f2cfcece0a538ce36b08e5e6 |
refresh_token | The refresh token issued for a given request. The refresh token could be used within refresh_expires_in period. The time unit is seconds. | ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6 |
token_type | The type of the access token. | Bearer |
expires_in | The time within the access token is valid. The time unit is seconds. | 120 |
scope | The scope/s of the access token. | basic |
refresh_expires_in | The time within the refresh token is valid. The time unit is seconds. | 300 |
Here are examples for different responses returned by the service.
- Successful response
Status Code: 200 OKWhen a refresh token of an access token is used, the access token will be invalidated and the new one should be used instead.
Content-Type: application/json
{
"access_token": "9ae75e11888d962ce1ffff1fa57cdc2a67380b89f2cfcece0a538ce36b08e5e6",
"refresh_token": "ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6",
"token_type": "Bearer",
"expires_in": "120",
"scope": "basic",
"refresh_expires_in": "300"
}
As you can see the response contains refresh_token and refresh_expires_in fields. A refresh token could be used (within refresh_expires_in period) to obtain a new access token without using username and password. - If the scope passed is not allowed for that client application, the scope does not exist or the scope is not allowed for the original access token issued
Status Code: 400 Bad Request
Content-Type: application/json
{"status":"scope not valid"}
- If the client_id or client_secret is not valid or the client application status is not active
Status Code: 400 Bad Request
Content-Type: application/json
{"error": "invalid client_id/client_secret"}
4.6 Define a custom grant_type
The ApiFest OAuth 2.0 Server enables you to define an additional to the RFC6749 (http://tools.ietf.org/html/rfc6749) grant type, if for some reasons you need one.Let's say we need to define an OTP custom grant type. For that reason, set the value of the custom.grant_type property in the properties file to otp. Also, a custom class that handles that flow should be implemented. The class must implement com.apifest.oauth20.api.ICustomGrantTypeHandler interface defined in apifest-oauth20-api.
Here is an example class that handles a custom grant type.
public class OTPGrantTypeHandler implements ICustomGrantTypeHandler {
@Override
public UserDetails execute(HttpRequest request) throws AuthenticationException {
// do some specific checks
doChecks(request);
//call OTP Service
HttpResponse response = callOTPService(request);
//construct the appropriate user details
UserDetails userDetails = createUserDetails(response);
return userDetails;
}
private void doChecks(HttpRequest request) {
...
}
private UserDetails createUserDetails(HttpResponse response) {
...
}
private HttpResponse callOTPService(HttpRequest request) {
...
}
}
@Override
public UserDetails execute(HttpRequest request) throws AuthenticationException {
// do some specific checks
doChecks(request);
//call OTP Service
HttpResponse response = callOTPService(request);
//construct the appropriate user details
UserDetails userDetails = createUserDetails(response);
return userDetails;
}
private void doChecks(HttpRequest request) {
...
}
private UserDetails createUserDetails(HttpResponse response) {
...
}
private HttpResponse callOTPService(HttpRequest request) {
...
}
}
The class should be bundled in the same jar where the implementation of the custom authentication is. The fully qualified name of the class should be set to custom.grant_type.class property. Let's say the name of the jar is custom-classes.jar, then for custom grant type activation the following properties should be set in the ApiFest OAuth 2.0 Server properties file:
custom.classes.jar=/home/apifest/custom-classes.jar
custom.grant_type=otp
custom.grant_type.class=com.apifest.example.OTPGrantTypeHandler
custom.grant_type=otp
custom.grant_type.class=com.apifest.example.OTPGrantTypeHandler