Authentication

plone.restapi uses Plone PAS 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’d set up a session with basic auth like this:

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.

http

POST /plone/@login HTTP/1.1
Accept: application/json
Content-Type: application/json

{
    "login": "admin",
    "password": "secret"
}

curl

curl -i -X POST http://nohost/plone/@login -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"login": "admin", "password": "secret"}'

httpie

echo '{
  "login": "admin",
  "password": "secret"
}' | http POST http://nohost/plone/@login Accept:application/json Content-Type:application/json

python-requests

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.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0"
}

Authenticating with a token

The token can now be used in subsequent requests by including it in the Authorization header with the Bearer scheme:

http

GET /plone/ HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0

curl

curl -i http://nohost/plone/ -H 'Accept: application/json' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0'

httpie

http http://nohost/plone/ Accept:application/json Authorization:'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0'

python-requests

requests.get('http://nohost/plone/', headers={
    'Accept': 'application/json',
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0',
})

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 simply post to the @login-renew endpoint.

http

POST /plone/@login-renew HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0

curl

curl -i -X POST http://nohost/plone/@login-renew -H 'Accept: application/json' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0'

httpie

http POST http://nohost/plone/@login-renew Accept:application/json Authorization:'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0'

python-requests

requests.post('http://nohost/plone/@login-renew', headers={
    'Accept': 'application/json',
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0',
})

The server returns a JSON object with a new token:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0"
}

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_tokes 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.

http

POST /plone/@logout HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0

curl

curl -i -X POST http://nohost/plone/@logout -H 'Accept: application/json' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0'

httpie

http POST http://nohost/plone/@logout Accept:application/json Authorization:'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0'

python-requests

requests.post('http://nohost/plone/@logout', headers={
    'Accept': 'application/json',
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.RVl8ZFJWIaA-8ujyulJvw0j3F3qFjIHDIJFK0GF6j_0',
})

If invalidation succeeds, the server responds with an empty 204 reponse:

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, so 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, and so on.

In order to modify/override this behavior, if your custom service class inherits from plone.restapi.services.Service, just override the method check_permission and add your custom checks accordingly.