blob: 3b02d985a8ad3627115cecb16b2b581d1827db03 [file] [log] [blame]
= Apache OFBiz® plugin for REST
This initial implementation helps to expose existing or new OFBiz services as REST.
To facilitate this, added a new "action" attribute to service elements that helps to determine how a particular service can be accessed via REST.
== Important URLs
* API https://localhost:8443/rest
* WADL https://localhost:8443/rest/application.wadl
* OpenAPI docs https://localhost:8443/docs/swagger-ui.html
== Endpoints
Once deployed, following URLs can be accessed
* GET /rest/services
* GET /rest/services/{serviceName}?inParams=<URLEncodedJSON>
* POST /rest/services/{serviceName} (For this endpoint, the service in parameters must be part of Request Body)
== Authentication
API is protected by JWT tokens, although other forms of access tokens may be supported in future.
An API client must first needs to authenticate itself using Basic Auth using username and password.
Username is nothing but OFBiz 'userLogin'. Token can also be generated using Swagger UI.
[source, json]
----
curl -X POST "https://localhost:8443/rest/auth/token" -H "accept: application/json" -H "Authorization: Basic YWRtaW46b2ZiaXo="
----
If successfully validated, generated token should be received in response
[source, json]
----
{
"statusCode": 200,
"statusDescription": "OK",
"successMessage": "Token granted.",
"data": {
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ1c2VyTG9naW5JZCI6ImFkbWluIiwiaXNzIjoiQXBhY2hlT0ZCaXoiLCJleHAiOjE1OTY3MDk4MjAsImlhdCI6MTU5NjcwODAyMH0.9Hj4pkkeQowAMxPLrI_To0WTohxxgVR6FoViyx5HoboTACQZ4iqDyqiIBodkuCVsZwOTPT1RSAQJ0L_oSVMqBA",
"token_type": "Bearer",
"expires_in": "1800"
}
}
----
Subsequent API calls must use received token using Bearer Authentication scheme as shown below -
[source, json]
----
GET /rest/services HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ1c2VyTG9naW5JZCI6ImFkbWluIiwiaXNzIjoiQXBhY2hlT0ZCaXoiLCJleHAiOjE1OTY3MDk4MjAsImlhdCI6MTU5NjcwODAyMH0.9Hj4pkkeQowAMxPLrI_To0WTohxxgVR6FoViyx5HoboTACQZ4iqDyqiIBodkuCVsZwOTPT1RSAQJ0L_oSVMqBA
----
== Example
* List All Services (export="true" and verb = "get|post")
[source, json]
----
GET /rest/services HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJBcGFjaGVPRkJpeiIsImlhdCI6MTU0NzczOTM0OCwiZXhwIjoxNjc5Mjc1MzQ4LCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiR2l2ZW5OYW1lIjoiSm9obm55IiwiU3VybmFtZSI6IlJvY2tldCIsIkVtYWlsIjoianJvY2tldEBleGFtcGxlLmNvbSIsInVzZXJMb2dpbklkIjoiYWRtaW4iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.fwafgrgpodBJcXxNTQdZknKeWKb3sDOsQrcR2vcRw97FznD6mkE79p10Tu7cqpUx7LiXuROUAnXEgqDice-BSg
----
Response :
[source, json]
----
{
"statusCode" : 200,
"statusDescription" : "OK",
"successMessage" : "OK",
"data" : [ {
"name" : "findProductById",
"description" : "Finds productId(s) corresponding to a product reference, productId or a GoodIdentification idValue",
"link" : {
"href" : "https://localhost:8443/rest/services/findProductById",
"rel" : "self",
"type" : "get"
}
}, {
"name" : "searchProductsByGoodIdentificationValue",
"description" : "Search Products by Good Identification Value",
"link" : {
"href" : "https://localhost:8443/rest/services/searchProductsByGoodIdentificationValue",
"rel" : "self",
"type" : "post"
}
} ]
}
----
* Call OFBiz service via GET(export="true" and verb = "get|post")
[source, json]
----
GET /rest/services/findProductById?inParams=%7B%22idToFind%22:%22GZ-1001%22%7D HTTP/1.1 +
Content-Type: application/json +
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJBcGFjaGVPRkJpeiIsImlhdCI6MTU0NzczOTM0OCwiZXhwIjoxNjc5Mjc1MzQ4LCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiR2l2ZW5OYW1lIjoiSm9obm55IiwiU3VybmFtZSI6IlJvY2tldCIsIkVtYWlsIjoianJvY2tldEBleGFtcGxlLmNvbSIsInVzZXJMb2dpbklkIjoiYWRtaW4iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.fwafgrgpodBJcXxNTQdZknKeWKb3sDOsQrcR2vcRw97FznD6mkE79p10Tu7cqpUx7LiXuROUAnXEgqDice-BSg
----
Response :
[source, json]
----
{
"statusCode" : 200,
"statusDescription" : "OK",
"data" : {
"product" : {
"createdStamp" : "2020-06-11T15:29:58.182+0000",
"productName" : "Nan Gizmo",
"createdByUserLogin" : "admin",
"productId" : "GZ-1001",
"taxable" : "Y",
"primaryProductCategoryId" : "101",
"createdTxStamp" : "2020-06-11T15:29:57.523+0000",
"lastUpdatedTxStamp" : "2020-06-11T15:29:57.523+0000",
"isVirtual" : "N",
"autoCreateKeywords" : "Y",
"description" : "Indian style Nan gizmo",
"chargeShipping" : "Y",
"internalName" : "Nan Gizmo",
"lastModifiedByUserLogin" : "admin",
"lastUpdatedStamp" : "2020-06-11T15:29:58.182+0000",
"lastModifiedDate" : "2001-05-13T06:30:00.000+0000",
"productTypeId" : "FINISHED_GOOD",
"createdDate" : "2001-05-13T06:30:00.000+0000",
"isVariant" : "N"
}
}
}
----
* Call OFBiz Service via POST +
[source, json]
----
POST rest/services/searchProductsByGoodIdentificationValue HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJBcGFjaGVPRkJpeiIsImlhdCI6MTU0NzczOTM0OCwiZXhwIjoxNjc5Mjc1MzQ4LCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiR2l2ZW5OYW1lIjoiSm9obm55IiwiU3VybmFtZSI6IlJvY2tldCIsIkVtYWlsIjoianJvY2tldEBleGFtcGxlLmNvbSIsInVzZXJMb2dpbklkIjoiYWRtaW4iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.fwafgrgpodBJcXxNTQdZknKeWKb3sDOsQrcR2vcRw97FznD6mkE79p10Tu7cqpUx7LiXuROUAnXEgqDice-BSg
{
"idFragment": "2644"
}
----
Response +
[source, json]
----
{
"statusCode" : 200,
"statusDescription" : "OK",
"data" : {
"products" : [ {
"internalName" : "Round Gizmo",
"productId" : "GZ-2644",
"primaryProductCategoryId" : "101",
"isVariant" : "N",
"goodIdentificationTypeId" : "INVOICE_EXPORT",
"idValue" : "GZ-2644-replaced",
"isVirtual" : "N"
} ]
}
}
----
== Refresh Token Mechanism
The authentication system now supports a refresh token mechanism.
This allows clients to obtain a new `access_token` using a previously issued `refresh_token`.
This improves user experience by reducing frequent authentication prompts.
It helps in maintaining seamless user sessions while minimising exposure of credentials,
making authentication more efficient and secure.
[CAUTION]
====
Of course it's not that simple on the security side. Because a `refresh_token` can be stolen!
This is well explained by this https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/[auth0.com article].
====
To preserve security auth0 is using a https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation[rotation mechanism] that sounds like a wise solution.
This is not implement OOTB in OFBiz, so you might implement it or possibly use auth0 for that.
The idea is to have short term refresh tokens replaced each time a refresh token is called. Here is an Auth0 article excerpt:
[TIP]
====
With refresh token rotation enabled in the Auth0 Dashboard, every time an application exchanges a refresh token to get a new access token, a new refresh token is also returned. Therefore, you no longer have a long-lived refresh token that, if compromised, could provide illegitimate access to resources. As refresh tokens are continually exchanged and invalidated, the threat is reduced.
====
The complete process is explained simply but well https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/#Refresh-Token-Automatic-Reuse-Detection[here].
A priori, this would be while calling AuthenticationResource::refreshToken. BTW this allows to store refresh tokens in local storage, which is quite safe.
=== Login API Enhancement
This said, upon successful login, the response includes both `access_token` and `refresh_token`.
.Response Example
[source,json]
----
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "dGhpc19pc19hX3JlZnJlc2hfdG9rZW4..."
}
----
=== Get Access Token Using Refresh Token
A new endpoint has been introduced to obtain a new `access_token` using a valid `refresh_token`.
*URL:* `POST https://localhost:8443/rest/auth/refresh-token`
==== Request
The request should contain the `refresh_token` in the body.
.Request Example
[source,json]
----
{
"Refresh-Token": "dGhpc19pc19hX3JlZnJlc2hfdG9rZW4..."
}
----
==== Response
If the refresh token is valid, a new `access_token` is returned.
.Response Example
[source,json]
----
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
----
==== Errors
- `401 Unauthorized`: If the provided refresh token is missing, expired, or invalid.