Skip to content

Commit

Permalink
Add documentation for JWT
Browse files Browse the repository at this point in the history
  • Loading branch information
buchi committed May 24, 2016
1 parent de6c93b commit b49cb09
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 3 deletions.
9 changes: 9 additions & 0 deletions docs/source/_json/login.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
POST /plone/@login
Accept: application/json

HTTP 200 OK
content-type: application/json

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.SZDnl_baH5M_StJJrzfbj7o-5My30NmSFbMrhpSX5I4"
}
9 changes: 9 additions & 0 deletions docs/source/_json/login_renew.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
POST /plone/@login-renew
Accept: application/json

HTTP 200 OK
content-type: application/json

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.SZDnl_baH5M_StJJrzfbj7o-5My30NmSFbMrhpSX5I4"
}
5 changes: 5 additions & 0 deletions docs/source/_json/logout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
POST /plone/@logout
Accept: application/json

HTTP 204 No Content

89 changes: 88 additions & 1 deletion docs/source/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,91 @@ Or the same example using ``curl``:

.. code:: python
curl -u username:password -H 'Accept:application/json' $URL
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.

A JWT token can be acquired by posting a users credentials to the ``@login``
endpoint.

.. example-code::

.. code-block:: http-request
POST /@login HTTP/1.1
Accept: application/json
Content-Type: application/json
{
'login': 'admin',
'password': 'admin',
}
.. code-block:: curl
curl -i \
-X POST \
-H "Accept: application/json" \
-H "Content-type: application/json" \
--data-raw '{"login":"admin", "password": "admin"}' \
http://localhost:8080/Plone/@login
.. code-block:: python-requests
requests.post('http://localhost:8080/Plone/@login',
headers={'Accept': 'application/json', 'Content-Type': 'application/json'},
data={'login': 'admin', 'password': 'admin'})
The server responds with a JSON object containing the token.

.. literalinclude:: _json/login.json
:language: js

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

.. code::
GET /Plone HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIiwiZXhwIjoxNDY0MDQyMTAzfQ.aOyvMwdcIMV6pzC0GYQ3ZMdGaHR1_W7DxT0W0ok4FxI
Accept: application/json
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.

.. code::
POST /@login-renew HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIiwiZXhwIjoxNDY0MDQyMTAzfQ.aOyvMwdcIMV6pzC0GYQ3ZMdGaHR1_W7DxT0W0ok4FxI
Accept: application/json
The server returns a JSON object with a new token:

.. literalinclude:: _json/login_renew.json
:language: js


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.

.. code::
POST /@logout HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIiwiZXhwIjoxNDY0MDQyMTAzfQ.aOyvMwdcIMV6pzC0GYQ3ZMdGaHR1_W7DxT0W0ok4FxI
Accept: application/json
If invalidation succeeds, the server responds with an empty 204 reponse:

.. literalinclude:: _json/logout.json
:language: js
49 changes: 47 additions & 2 deletions src/plone/restapi/tests/test_documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
from zope.component import getUtility
from zope.intid.interfaces import IIntIds

import unittest2 as unittest

import json
import os
import unittest2 as unittest

REQUEST_HEADER_KEYS = [
'accept'
Expand Down Expand Up @@ -300,3 +300,48 @@ def test_documentation_types(self):
def test_documentation_types_document(self):
response = self.api_session.get('@types/Document')
save_response_for_documentation('types_document.json', response)

def test_documentation_login(self):
self.portal.acl_users.jwt_auth._secret = 'secret'
self.portal.acl_users.jwt_auth.use_keyring = False
self.portal.acl_users.jwt_auth.token_timeout = 0
import transaction
transaction.commit()
self.api_session.auth = None
response = self.api_session.post(
'{}/@login'.format(self.portal.absolute_url()),
json={'login': SITE_OWNER_NAME, 'password': SITE_OWNER_PASSWORD})
save_response_for_documentation('login.json', response)

def test_documentation_login_renew(self):
self.portal.acl_users.jwt_auth._secret = 'secret'
self.portal.acl_users.jwt_auth.use_keyring = False
self.portal.acl_users.jwt_auth.token_timeout = 0
import transaction
transaction.commit()
self.api_session.auth = None
response = self.api_session.post(
'{}/@login'.format(self.portal.absolute_url()),
json={'login': SITE_OWNER_NAME, 'password': SITE_OWNER_PASSWORD})
token = json.loads(response.content)['token']
response = self.api_session.post(
'{}/@login-renew'.format(self.portal.absolute_url()),
headers={'Authorization': 'Bearer {}'.format(token)})
save_response_for_documentation('login_renew.json', response)

def test_documentation_logout(self):
self.portal.acl_users.jwt_auth._secret = 'secret'
self.portal.acl_users.jwt_auth.use_keyring = False
self.portal.acl_users.jwt_auth.token_timeout = 0
self.portal.acl_users.jwt_auth.store_tokens = True
import transaction
transaction.commit()
self.api_session.auth = None
response = self.api_session.post(
'{}/@login'.format(self.portal.absolute_url()),
json={'login': SITE_OWNER_NAME, 'password': SITE_OWNER_PASSWORD})
token = json.loads(response.content)['token']
response = self.api_session.post(
'{}/@logout'.format(self.portal.absolute_url()),
headers={'Authorization': 'Bearer {}'.format(token)})
save_response_for_documentation('logout.json', response)

0 comments on commit b49cb09

Please sign in to comment.