Authentication#
plone.restapi
uses PlonePAS
for authentication.
That means that any authentication method supported by an installed PAS plugin should work, assuming it's an authentication method that makes sense to use with an API.
For example, to authenticate using HTTP basic auth, you'd set an Authorization
header:
GET /Plone HTTP/1.1
Authorization: Basic Zm9vYmFyOmZvb2Jhcgo=
Accept: application/json
HTTP client libraries usually contain helper functions to produce a proper Authorization
header for you based on given credentials.
Using the requests
library, you would set up a session with basic authentication as follows:
import requests
session = requests.Session()
session.auth = ('username', 'password')
session.headers.update({'Accept': 'application/json'})
response = session.get(url)
Or the same example using curl
:
curl -u username:password -H 'Accept:application/json' $URL
JSON Web Tokens (JWT)#
plone.restapi
includes a Plone PAS plugin for authentication with JWT.
The plugin is installed automatically when installing the product.
Acquiring a token (@login)#
A JWT token can be acquired by posting a user's credentials to the @login
endpoint:
POST /plone/@login HTTP/1.1
Accept: application/json
Content-Type: application/json
{
"login": "admin",
"password": "secret"
}
curl -i -X POST http://nohost/plone/@login -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"login": "admin", "password": "secret"}'
echo '{
"login": "admin",
"password": "secret"
}' | http POST http://nohost/plone/@login Accept:application/json Content-Type:application/json
requests.post('http://nohost/plone/@login', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'login': 'admin', 'password': 'secret'})
The server responds with a JSON object containing the token:
HTTP/1.1 200 OK
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg"
}
Authenticating with a token#
The token can now be used in subsequent requests by including it in the Authorization
header with the Bearer
scheme:
GET /plone/ HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg
curl -i -X GET http://nohost/plone/ -H "Accept: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg"
http http://nohost/plone/ Accept:application/json Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg"
requests.get('http://nohost/plone/', headers={'Accept': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg'})
Renewing a token (@login-renew)#
By default, the token will expire after 12 hours, and thus must be renewed before expiration.
To renew the token, POST
to the @login-renew
endpoint:
POST /plone/@login-renew HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg
curl -i -X POST http://nohost/plone/@login-renew -H "Accept: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg"
http POST http://nohost/plone/@login-renew Accept:application/json Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg"
requests.post('http://nohost/plone/@login-renew', headers={'Accept': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg'})
The server returns a JSON object with a new token:
HTTP/1.1 200 OK
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg"
}
Invalidating a token (@logout)#
The @logout
endpoint can be used to invalidate tokens.
However by default tokens are not persisted on the server and thus can not be invalidated.
To enable token invaldiation, activate the store_tokens
option in the PAS plugin.
If you need tokens that are valid indefinitely you should also disable the use of Plone's keyring in the PAS plugin (option use_keyring
).
The logout request must contain the existing token in the Authorization
header:
POST /plone/@logout HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg
curl -i -X POST http://nohost/plone/@logout -H "Accept: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg"
http POST http://nohost/plone/@logout Accept:application/json Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg"
requests.post('http://nohost/plone/@logout', headers={'Accept': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiIn0.S9kUg8j-Iju0eaOpot7asXiZO8mlJX1fQVt9MPQpXBg'})
If invalidation succeeds, the server responds with an empty 204 response:
HTTP/1.1 204 No Content
Permissions#
In order for a user to use the REST API, the plone.restapi: Use REST API
permission is required.
By default, installing the plone.restapi:default
profile will assign this permission to the Anonymous
role.
Everybody is allowed to use the REST API by default.
If you wish to control in more detail which roles are allowed to use the REST API, please assign this permission accordingly.
As well as the plone.restapi: Use REST API
permission, some of the common Plone permissions are also required, depending on the particular service.
For example, retrieving a resource using GET
will require View
.
Adding an object using POST
will require Add portal content
.
In order to modify or override this behavior, if your custom service class inherits from plone.restapi.services.Service
, override the method check_permission
and add your custom checks accordingly.