summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Adams <mark@markadams.me>2015-10-24 20:07:47 -0500
committerMark Adams <mark@markadams.me>2015-10-24 20:07:47 -0500
commit9c551e754d3edf1d500296b172e85904dc593006 (patch)
treece391ef2789a9b631d8db4cf77ae2d560d8f8574
parentd505613e11ebcf7288d5d6768a0279d420625b37 (diff)
parentf69a4c0c594ff1460bd469b76031937a328dab42 (diff)
downloadpyjwt-9c551e754d3edf1d500296b172e85904dc593006.tar.gz
Merge pull request #186 from jpadilla/add-docs
Make documentation awesome and add support for "Read the Docs"
-rw-r--r--README.md349
-rw-r--r--docs/Makefile192
-rw-r--r--docs/algorithms.rst56
-rw-r--r--docs/api.rst54
-rw-r--r--docs/conf.py297
-rw-r--r--docs/faq.rst25
-rw-r--r--docs/index.rst43
-rw-r--r--docs/installation.rst53
-rw-r--r--docs/usage.rst189
-rw-r--r--setup.cfg1
10 files changed, 917 insertions, 342 deletions
diff --git a/README.md b/README.md
index 0dbe758..fc53c1b 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,9 @@
[![appveyor-status-image]][appveyor]
[![pypi-version-image]][pypi]
[![coveralls-status-image]][coveralls]
+[![docs-status-image]][docs]
-A Python implementation of [JSON Web Token draft 32][jwt-spec].
+A Python implementation of [RFC 7519][jwt-spec].
Original implementation was written by [@progrium][progrium].
## Installing
@@ -14,114 +15,15 @@ Original implementation was written by [@progrium][progrium].
$ pip install PyJWT
```
-**A Note on Dependencies**:
-
-RSA and ECDSA signatures depend on the recommended `cryptography` package (0.8+). If you plan on
-using any of those algorithms, you'll need to install it as well.
-
-```
-$ pip install cryptography
-```
-
-If your system doesn't allow installing `cryptography` like on Google App Engine, you can install `PyCrypto` for RSA signatures and `ecdsa` for ECDSA signatures.
-
## Usage
```python
>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
-```
-
-Additional headers may also be specified.
-
-```python
->>> jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'})
-'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJzb21lIjoicGF5bG9hZCJ9.DogbDGmMHgA_bU05TAB-R6geQ2nMU2BRM-LnYEtefwg'
-```
-
-Note the resulting JWT will not be encrypted, but verifiable with a secret key.
-
-```python
->>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
-{u'some': u'payload'}
-```
-
-If the secret is wrong, it will raise a `jwt.DecodeError` telling you as such.
-You can still get the payload by setting the `verify` argument to `False`.
-
-```python
->>> jwt.decode(encoded, verify=False)
-{u'some': u'payload'}
-```
-
-## Validation
-Exceptions can be raised during `decode()` for other errors besides an
-invalid signature (e.g. for invalid issuer or audience (see below). All
-exceptions that signify that the token is invalid extend from the base
-`InvalidTokenError` exception class, so applications can use this approach to
-catch any issues relating to invalid tokens:
-
-```python
-try:
- payload = jwt.decode(encoded)
-except jwt.InvalidTokenError:
- pass # do something sensible here, e.g. return HTTP 403 status code
-```
-
-### Skipping Claim Verification
-You may also override claim verification via the `options` dictionary. The
-default options are:
-
-```python
-options = {
- 'verify_signature': True,
- 'verify_exp': True,
- 'verify_nbf': True,
- 'verify_iat': True,
- 'verify_aud': True
- 'require_exp': False,
- 'require_iat': False,
- 'require_nbf': False
-}
-```
-
-You can skip validation of individual claims by passing an `options` dictionary
-with the "verify_<claim_name>" key set to `False` when you call `jwt.decode()`.
-For example, if you want to verify the signature of a JWT that has already
-expired, you could do so by setting `verify_exp` to `False`.
-
-```python
->>> options = {
->>> 'verify_exp': False,
->>> }
-
->>> encoded = '...' # JWT with an expired exp claim
->>> jwt.decode(encoded, 'secret', options=options)
-{u'some': u'payload'}
-```
-
-**NOTE**: *Changing the default behavior is done at your own risk, and almost
-certainly will make your application less secure. Doing so should only be done
-with a very clear understanding of what you are doing.*
-
-### Requiring Optional Claims
-In addition to skipping certain validations, you may also specify that certain
-optional claims are required by setting the appropriate `require_<claim_name>`
-option to True. If the claim is not present, PyJWT will raise a
-`jwt.exceptions.MissingRequiredClaimError`.
-
-For instance, the following code would require that the token has a 'exp'
-claim and raise an error if it is not present:
-```python
->>> options = {
->>> 'require_exp': True
->>> }
-
->>> encoded = '...' # JWT without an exp claim
->>> jwt.decode(encoded, 'secret', options=options)
-jwt.exceptions.MissingRequiredClaimError: Token is missing the "exp" claim
+>>> jwt.decode(encoded, 'secret'. algorithms=['HS256'])
+{'some': 'payload'}
```
## Tests
@@ -132,244 +34,6 @@ You can run tests from the project root after cloning with:
$ python setup.py test
```
-## Algorithms
-
-The JWT spec supports several algorithms for cryptographic signing. This library
-currently supports:
-
-* HS256 - HMAC using SHA-256 hash algorithm (default)
-* HS384 - HMAC using SHA-384 hash algorithm
-* HS512 - HMAC using SHA-512 hash algorithm
-* ES256 - ECDSA signature algorithm using SHA-256 hash algorithm
-* ES384 - ECDSA signature algorithm using SHA-384 hash algorithm
-* ES512 - ECDSA signature algorithm using SHA-512 hash algorithm
-* RS256 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-256 hash algorithm
-* RS384 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-384 hash algorithm
-* RS512 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-512 hash algorithm
-* PS256 - RSASSA-PSS signature using SHA-256 and MGF1 padding with SHA-256
-* PS384 - RSASSA-PSS signature using SHA-384 and MGF1 padding with SHA-384
-* PS512 - RSASSA-PSS signature using SHA-512 and MGF1 padding with SHA-512
-
-### Encoding
-You can specify which algorithm you would like to use to sign the JWT
-by using the `algorithm` parameter:
-
-```python
->>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS512')
-'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.WTzLzFO079PduJiFIyzrOah54YaM8qoxH9fLMQoQhKtw3_fMGjImIOokijDkXVbyfBqhMo2GCNu4w9v7UXvnpA'
-```
-
-### Decoding
-When decoding, you can specify which algorithms you would like to permit
-when validating the JWT by using the `algorithms` parameter which takes a list
-of allowed algorithms:
-
-```python
->>> jwt.decode(encoded, 'secret', algorithms=['HS512', 'HS256'])
-{u'some': u'payload'}
-```
-
-In the above case, if the JWT has any value for its alg header other than
-HS512 or HS256, the claim will be rejected with an `InvalidAlgorithmError`.
-
-### Asymmetric (Public-key) Algorithms
-Usage of RSA (RS\*) and EC (EC\*) algorithms require a basic understanding
-of how public-key cryptography is used with regards to digital signatures.
-If you are unfamiliar, you may want to read
-[this article](http://en.wikipedia.org/wiki/Public-key_cryptography).
-
-When using the RSASSA-PKCS1-v1_5 algorithms, the `key` argument in both
-`jwt.encode()` and `jwt.decode()` (`"secret"` in the examples) is expected to
-be either an RSA public or private key in PEM or SSH format. The type of key
-(private or public) depends on whether you are signing or verifying.
-
-When using the ECDSA algorithms, the `key` argument is expected to
-be an Elliptic Curve public or private key in PEM format. The type of key
-(private or public) depends on whether you are signing or verifying.
-
-
-## Support of registered claim names
-
-JSON Web Token defines some registered claim names and defines how they should
-be used. PyJWT supports these registered claim names:
-
- - "exp" (Expiration Time) Claim
- - "nbf" (Not Before Time) Claim
- - "iss" (Issuer) Claim
- - "aud" (Audience) Claim
- - "iat" (Issued At) Claim
-
-### Expiration Time Claim
-
-From [the JWT spec][jwt-spec-reg-claims]:
-
-> The "exp" (expiration time) claim identifies the expiration time on
-> or after which the JWT MUST NOT be accepted for processing. The
-> processing of the "exp" claim requires that the current date/time
-> MUST be before the expiration date/time listed in the "exp" claim.
-> Implementers MAY provide for some small leeway, usually no more than
-> a few minutes, to account for clock skew. Its value MUST be a number
-> containing a NumericDate value. Use of this claim is OPTIONAL.
-
-You can pass the expiration time as a UTC UNIX timestamp (an int) or as a
-datetime, which will be converted into an int. For example:
-
-```python
-jwt.encode({'exp': 1371720939}, 'secret')
-
-jwt.encode({'exp': datetime.utcnow()}, 'secret')
-```
-
-Expiration time is automatically verified in `jwt.decode()` and raises
-`jwt.ExpiredSignatureError` if the expiration time is in the past:
-
-```python
-import jwt
-
-try:
- jwt.decode('JWT_STRING', 'secret')
-except jwt.ExpiredSignatureError:
- # Signature has expired
-```
-
-Expiration time will be compared to the current UTC time (as given by
-`timegm(datetime.utcnow().utctimetuple())`), so be sure to use a UTC timestamp
-or datetime in encoding.
-
-You can turn off expiration time verification with the `verify_exp` parameter in the options argument.
-
-PyJWT also supports the leeway part of the expiration time definition, which
-means you can validate a expiration time which is in the past but not very far.
-For example, if you have a JWT payload with a expiration time set to 30 seconds
-after creation but you know that sometimes you will process it after 30 seconds,
-you can set a leeway of 10 seconds in order to have some margin:
-
-```python
-import datetime
-import time
-import jwt
-
-jwt_payload = jwt.encode({
- 'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=30)
-}, 'secret')
-
-time.sleep(32)
-
-# JWT payload is now expired
-# But with some leeway, it will still validate
-jwt.decode(jwt_payload, 'secret', leeway=10)
-```
-
-Instead of specifying the leeway as a number of seconds, a `datetime.timedelta`
-instance can be used. The last line in the example above is equivalent to:
-
-```python
-jwt.decode(jwt_payload, 'secret', leeway=datetime.timedelta(seconds=10))
-```
-
-
-### Not Before Time Claim
-
-> The "nbf" (not before) claim identifies the time before which the JWT
-> MUST NOT be accepted for processing. The processing of the "nbf"
-> claim requires that the current date/time MUST be after or equal to
-> the not-before date/time listed in the "nbf" claim. Implementers MAY
-> provide for some small leeway, usually no more than a few minutes, to
-> account for clock skew. Its value MUST be a number containing a
-> NumericDate value. Use of this claim is OPTIONAL.
-
-The `nbf` claim works similarly to the `exp` claim above.
-
-```python
-jwt.encode({'nbf': 1371720939}, 'secret')
-
-jwt.encode({'nbf': datetime.utcnow()}, 'secret')
-```
-
-### Issuer Claim
-
-> The "iss" (issuer) claim identifies the principal that issued the
-> JWT. The processing of this claim is generally application specific.
-> The "iss" value is a case-sensitive string containing a StringOrURI
-> value. Use of this claim is OPTIONAL.
-
-```python
-import jwt
-
-
-payload = {
- 'some': 'payload',
- 'iss': 'urn:foo'
-}
-
-token = jwt.encode(payload, 'secret')
-decoded = jwt.decode(token, 'secret', issuer='urn:foo')
-```
-
-If the issuer claim is incorrect, `jwt.InvalidIssuerError` will be raised.
-
-
-### Audience Claim
-
-> The "aud" (audience) claim identifies the recipients that the JWT is
-> intended for. Each principal intended to process the JWT MUST
-> identify itself with a value in the audience claim. If the principal
-> processing the claim does not identify itself with a value in the
-> "aud" claim when this claim is present, then the JWT MUST be
-> rejected. In the general case, the "aud" value is an array of case-
-> sensitive strings, each containing a StringOrURI value. In the
-> special case when the JWT has one audience, the "aud" value MAY be a
-> single case-sensitive string containing a StringOrURI value. The
-> interpretation of audience values is generally application specific.
-> Use of this claim is OPTIONAL.
-
-```python
-import jwt
-
-
-payload = {
- 'some': 'payload',
- 'aud': 'urn:foo'
-}
-
-token = jwt.encode(payload, 'secret')
-decoded = jwt.decode(token, 'secret', audience='urn:foo')
-```
-
-If the audience claim is incorrect, `jwt.InvalidAudienceError` will be raised.
-
-### Issued At Claim
-
-> The iat (issued at) claim identifies the time at which the JWT was issued.
-> This claim can be used to determine the age of the JWT. Its value MUST be a
-> number containing a NumericDate value. Use of this claim is OPTIONAL.
-
-If the `iat` claim is in the future, an `jwt.InvalidIssuedAtError` exception
-will be raised.
-
-```python
-jwt.encode({'iat': 1371720939}, 'secret')
-
-jwt.encode({'iat': datetime.utcnow()}, 'secret')
-```
-
-## Frequently Asked Questions
-
-**How can I extract a public / private key from a x509 certificate?**
-
-The `load_pem_x509_certificate()` function from `cryptography` can be used to
-extract the public or private keys from a x509 certificate in PEM format.
-
-```python
-from cryptography.x509 import load_pem_x509_certificate
-from cryptography.hazmat.backends import default_backend
-
-cert_str = "-----BEGIN CERTIFICATE-----MIIDETCCAfm..."
-cert_obj = load_pem_x509_certificate(cert_str, default_backend())
-public_key = cert_obj.public_key()
-private_key = cert_obj.private_key()
-```
-
[travis-status-image]: https://secure.travis-ci.org/jpadilla/pyjwt.svg?branch=master
[travis]: http://travis-ci.org/jpadilla/pyjwt?branch=master
[appveyor-status-image]: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true
@@ -378,6 +42,7 @@ private_key = cert_obj.private_key()
[pypi]: https://pypi.python.org/pypi/pyjwt
[coveralls-status-image]: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master
[coveralls]: https://coveralls.io/r/jpadilla/pyjwt?branch=master
-[jwt-spec]: https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32
-[jwt-spec-reg-claims]: http://self-issued.info/docs/draft-jones-json-web-token-01.html#ReservedClaimName
+[docs-status-image]: https://readthedocs.org/projects/pyjwt/badge/?version=latest
+[docs]: http://pyjwt.readthedocs.org
+[jwt-spec]: https://tools.ietf.org/html/rfc7519
[progrium]: https://github.com/progrium
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..40a745d
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,192 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " applehelp to make an Apple Help Book"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " xml to make Docutils-native XML files"
+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+ @echo " coverage to run coverage check of the documentation (if enabled)"
+
+clean:
+ rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyJWT.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyJWT.qhc"
+
+applehelp:
+ $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+ @echo
+ @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+ @echo "N.B. You won't be able to view it unless you put it in" \
+ "~/Library/Documentation/Help or install it in your application" \
+ "bundle."
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/PyJWT"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyJWT"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through platex and dvipdfmx..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
+
+coverage:
+ $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+ @echo "Testing of coverage in the sources finished, look at the " \
+ "results in $(BUILDDIR)/coverage/python.txt."
+
+xml:
+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+ @echo
+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+ @echo
+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/docs/algorithms.rst b/docs/algorithms.rst
new file mode 100644
index 0000000..69ed575
--- /dev/null
+++ b/docs/algorithms.rst
@@ -0,0 +1,56 @@
+Digital Signature Algorithms
+============================
+
+The JWT specification supports several algorithms for cryptographic signing.
+This library currently supports:
+
+* HS256 - HMAC using SHA-256 hash algorithm (default)
+* HS384 - HMAC using SHA-384 hash algorithm
+* HS512 - HMAC using SHA-512 hash algorithm
+* ES256 - ECDSA signature algorithm using SHA-256 hash algorithm
+* ES384 - ECDSA signature algorithm using SHA-384 hash algorithm
+* ES512 - ECDSA signature algorithm using SHA-512 hash algorithm
+* RS256 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-256 hash algorithm
+* RS384 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-384 hash algorithm
+* RS512 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-512 hash algorithm
+* PS256 - RSASSA-PSS signature using SHA-256 and MGF1 padding with SHA-256
+* PS384 - RSASSA-PSS signature using SHA-384 and MGF1 padding with SHA-384
+* PS512 - RSASSA-PSS signature using SHA-512 and MGF1 padding with SHA-512
+
+Asymmetric (Public-key) Algorithms
+----------------------------------
+Usage of RSA (RS\*) and EC (EC\*) algorithms require a basic understanding
+of how public-key cryptography is used with regards to digital signatures.
+If you are unfamiliar, you may want to read
+[this article](http://en.wikipedia.org/wiki/Public-key_cryptography).
+
+When using the RSASSA-PKCS1-v1_5 algorithms, the `key` argument in both
+``jwt.encode()`` and ``jwt.decode()`` (``"secret"`` in the examples) is expected to
+be either an RSA public or private key in PEM or SSH format. The type of key
+(private or public) depends on whether you are signing or verifying a token.
+
+When using the ECDSA algorithms, the ``key`` argument is expected to
+be an Elliptic Curve public or private key in PEM format. The type of key
+(private or public) depends on whether you are signing or verifying.
+
+Specifying an Algorithm
+-----------------------
+You can specify which algorithm you would like to use to sign the JWT
+by using the `algorithm` parameter:
+
+.. code-block:: python
+
+ >>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS512')
+ 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.WTzLzFO079PduJiFIyzrOah54YaM8qoxH9fLMQoQhKtw3_fMGjImIOokijDkXVbyfBqhMo2GCNu4w9v7UXvnpA'
+
+When decoding, you can also specify which algorithms you would like to permit
+when validating the JWT by using the `algorithms` parameter which takes a list
+of allowed algorithms:
+
+.. code-block:: python
+
+ >>> jwt.decode(encoded, 'secret', algorithms=['HS512', 'HS256'])
+ {u'some': u'payload'}
+
+In the above case, if the JWT has any value for its alg header other than
+HS512 or HS256, the claim will be rejected with an ``InvalidAlgorithmError``.
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 0000000..4bb4d3b
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,54 @@
+API Reference
+=============
+
+.. module:: jwt
+
+TODO: Document PyJWS / PyJWT classes
+
+Exceptions
+----------
+
+.. currentmodule:: jwt.exceptions
+
+
+.. class:: InvalidTokenError
+
+ Base exception when ``decode()`` fails on a token
+
+.. class:: DecodeError
+
+ Raised when a token cannot be decoded because it failed validation
+
+.. class:: ExpiredSignatureError
+
+ Raised when a token's ``exp`` claim indicates that it has expired
+
+.. class:: InvalidAudienceError
+
+ Raised when a token's ``aud`` claim does not match one of the expected
+ audience values
+
+.. class:: InvalidIssuerError
+
+ Raised when a token's ``iss`` claim does not match the expected issuer
+
+.. class:: InvalidIssuedAtError
+
+ Raised when a token's ``iat`` claim is in the future
+
+.. class:: ImmatureSignatureError
+
+ Raised when a token's ``nbf`` claim represents a time in the future
+
+.. class:: InvalidKeyError
+
+ Raised when the specified key is not in the proper format
+
+.. class:: InvalidAlgorithmError
+
+ Raised when the specified algorithm is not recognized by PyJWT
+
+.. class:: MissingRequiredClaimError
+
+ Raised when a claim that is required to be present is not contained
+ in the claimset
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..ae183e4
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,297 @@
+# -*- coding: utf-8 -*-
+#
+# PyJWT documentation build configuration file, created by
+# sphinx-quickstart on Thu Oct 22 18:11:10 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys
+import os
+import shlex
+import re
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'PyJWT'
+copyright = u'2015, José Padilla'
+author = u'José Padilla'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+def get_version(package):
+ """
+ Return package version as listed in `__version__` in `init.py`.
+ """
+ with open(os.path.join('..', package, '__init__.py'), 'rb') as init_py:
+ src = init_py.read().decode('utf-8')
+ return re.search("__version__ = ['\"]([^'\"]+)['\"]", src).group(1)
+
+version = get_version('jwt')
+# The full version, including alpha/beta/rc tags.
+release = version
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+import sphinx_rtd_theme
+
+html_theme = "sphinx_rtd_theme"
+
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Language to be used for generating the HTML full-text search index.
+# Sphinx supports the following languages:
+# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
+# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
+#html_search_language = 'en'
+
+# A dictionary with options for the search language support, empty by default.
+# Now only 'ja' uses this config value
+#html_search_options = {'type': 'default'}
+
+# The name of a javascript file (relative to the configuration directory) that
+# implements a search results scorer. If empty, the default will be used.
+#html_search_scorer = 'scorer.js'
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'PyJWTdoc'
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+
+# Latex figure (float) alignment
+#'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'PyJWT.tex', u'PyJWT Documentation',
+ u'José Padilla', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'pyjwt', u'PyJWT Documentation',
+ [author], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'PyJWT', u'PyJWT Documentation',
+ author, 'PyJWT', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
diff --git a/docs/faq.rst b/docs/faq.rst
new file mode 100644
index 0000000..59a6cb6
--- /dev/null
+++ b/docs/faq.rst
@@ -0,0 +1,25 @@
+Frequently Asked Questions
+==========================
+
+How can I extract a public / private key from a x509 certificate?
+-----------------------------------------------------------------
+
+The ``load_pem_x509_certificate()`` function from ``cryptography`` can be used to
+extract the public or private keys from a x509 certificate in PEM format.
+
+.. code-block:: python
+
+ from cryptography.x509 import load_pem_x509_certificate
+ from cryptography.hazmat.backends import default_backend
+
+ cert_str = "-----BEGIN CERTIFICATE-----MIIDETCCAfm..."
+ cert_obj = load_pem_x509_certificate(cert_str, default_backend())
+ public_key = cert_obj.public_key()
+ private_key = cert_obj.private_key()
+
+I'm using Google App Engine and can't install `cryptography`, what can I do?
+----------------------------------------------------------------------------
+
+Some platforms like Google App Engine don't allow you to install libraries
+that require C extensions to be built (like `cryptography`). If you're deploying
+to one of those environments, you should check out :ref:`legacy-deps`
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..782031a
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,43 @@
+Welcome to ``PyJWT``
+===========================
+
+``PyJWT`` is a Python library which allows you to encode and decode JSON Web
+Tokens (JWT). JWT is an open, industry-standard (`RFC 7519`_) for representing
+claims securely between two parties.
+
+Installation
+------------
+You can install ``pyjwt`` with ``pip``:
+
+.. code-block:: console
+
+ $ pip install pyjwt
+
+See :doc:`Installation <installation>` for more information.
+
+Example Usage
+------------
+
+.. code-block:: python
+
+ >>> import jwt
+
+ >>> jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
+
+ >>> jwt.decode('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg', 'secret')
+ {'some': 'payload'}
+
+
+See :doc:`Usage Examples <usage>` for more examples.
+
+.. toctree::
+ :maxdepth: 2
+
+ installation
+ usage
+ faq
+ algorithms
+ api
+
+.. _`RFC 7519`: https://tools.ietf.org/html/rfc7519
diff --git a/docs/installation.rst b/docs/installation.rst
new file mode 100644
index 0000000..b60e06c
--- /dev/null
+++ b/docs/installation.rst
@@ -0,0 +1,53 @@
+Installation
+============
+
+You can install ``PyJWT`` with ``pip``:
+
+.. code-block:: console
+
+ $ pip install pyjwt
+
+Cryptographic Dependencies (Optional)
+-------------------
+
+If you are planning on encoding or decoding tokens using certain digital
+signature algorithms (like RSA or ECDSA), you will need to install the
+cryptography_ library.
+
+.. code-block:: console
+
+ $ pip install cryptography
+
+.. _legacy-deps:
+
+Legacy Dependencies
+-------------------
+
+Some environments, most notably Google App Engine, do not allow the installation
+of Python packages that require compilation of C extensions and therefore
+cannot install ``cryptography``. If you can install ``cryptography``, you
+should disregard this section.
+
+If you are deploying an application to one of these environments, you may
+need to use the legacy implementations of the digital signature algorithms:
+
+.. code-block:: console
+
+ $ pip install pycrypto ecdsa
+
+Once you have installed ``pycrypto`` and ``ecdcsa``, you can tell PyJWT to use
+the legacy implementations with ``jwt.register_algorithm()``. The following
+example code shows how to configure PyJWT to use the legacy implementations
+for RSA with SHA256 and EC with SHA256 signatures.
+
+.. code-block:: python
+
+ import jwt
+ import jwt.contrib.algorithms.pycrypto import RSAAlgorithm
+ import jwt.contrib.algorithms.py_ecdsa import ECAlgorithm
+
+ jwt.register_algorithm('RS256', RSAAlgorithm(RSAAlgorithm.SHA256))
+ jwt.register_algorithm('ES256', ECAlgorithm(ECAlgorithm.SHA256))
+
+
+.. _`cryptography`: https://cryptography.io
diff --git a/docs/usage.rst b/docs/usage.rst
new file mode 100644
index 0000000..c2e286c
--- /dev/null
+++ b/docs/usage.rst
@@ -0,0 +1,189 @@
+Usage Examples
+==============
+
+Encoding & Decoding Tokens
+---------------------------------
+
+.. code-block:: python
+
+ >>import jwt
+ >>encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
+
+
+Specifying Additional Headers
+---------------------------------
+
+.. code-block:: python
+
+ >>jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'})
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJzb21lIjoicGF5bG9hZCJ9.DogbDGmMHgA_bU05TAB-R6geQ2nMU2BRM-LnYEtefwg'
+
+
+Reading the Claimset without Validation
+-----------------------------------------
+
+If you wish to read the claimset of a JWT without performing validation of the
+signature or any of the registered claim names, you can set the ``verify``
+parameter to ``False``.
+
+Note: It is generally ill-advised to use this functionality unless you
+clearly understand what you are doing. Without digital signature information,
+the integrity or authenticity of the claimset cannot be trusted.
+
+.. code-block:: python
+
+ >>jwt.decode(encoded, verify=False)
+ {u'some': u'payload'}
+
+Registered Claim Names
+----------------------
+
+The JWT specificaftion defines some registered claim names and defines
+how they should be used. PyJWT supports these registered claim names:
+
+ - "exp" (Expiration Time) Claim
+ - "nbf" (Not Before Time) Claim
+ - "iss" (Issuer) Claim
+ - "aud" (Audience) Claim
+ - "iat" (Issued At) Claim
+
+Expiration Time Claim (exp)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The "exp" (expiration time) claim identifies the expiration time on
+ or after which the JWT MUST NOT be accepted for processing. The
+ processing of the "exp" claim requires that the current date/time
+ MUST be before the expiration date/time listed in the "exp" claim.
+ Implementers MAY provide for some small leeway, usually no more than
+ a few minutes, to account for clock skew. Its value MUST be a number
+ containing a NumericDate value. Use of this claim is OPTIONAL.
+
+You can pass the expiration time as a UTC UNIX timestamp (an int) or as a
+datetime, which will be converted into an int. For example:
+
+.. code-block:: python
+
+ jwt.encode({'exp': 1371720939}, 'secret')
+ jwt.encode({'exp': datetime.utcnow()}, 'secret')
+
+Expiration time is automatically verified in `jwt.decode()` and raises
+`jwt.ExpiredSignatureError` if the expiration time is in the past:
+
+.. code-block:: python
+
+ try:
+ jwt.decode('JWT_STRING', 'secret')
+ except jwt.ExpiredSignatureError:
+ # Signature has expired
+
+Expiration time will be compared to the current UTC time (as given by
+`timegm(datetime.utcnow().utctimetuple())`), so be sure to use a UTC timestamp
+or datetime in encoding.
+
+You can turn off expiration time verification with the `verify_exp` parameter in the options argument.
+
+PyJWT also supports the leeway part of the expiration time definition, which
+means you can validate a expiration time which is in the past but not very far.
+For example, if you have a JWT payload with a expiration time set to 30 seconds
+after creation but you know that sometimes you will process it after 30 seconds,
+you can set a leeway of 10 seconds in order to have some margin:
+
+.. code-block:: python
+
+ jwt_payload = jwt.encode({
+ 'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=30)
+ }, 'secret')
+
+ time.sleep(32)
+
+ # JWT payload is now expired
+ # But with some leeway, it will still validate
+ jwt.decode(jwt_payload, 'secret', leeway=10)
+
+Instead of specifying the leeway as a number of seconds, a `datetime.timedelta`
+instance can be used. The last line in the example above is equivalent to:
+
+.. code-block:: python
+
+ jwt.decode(jwt_payload, 'secret', leeway=datetime.timedelta(seconds=10))
+
+Not Before Time Claim (nbf)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The "nbf" (not before) claim identifies the time before which the JWT
+ MUST NOT be accepted for processing. The processing of the "nbf"
+ claim requires that the current date/time MUST be after or equal to
+ the not-before date/time listed in the "nbf" claim. Implementers MAY
+ provide for some small leeway, usually no more than a few minutes, to
+ account for clock skew. Its value MUST be a number containing a
+ NumericDate value. Use of this claim is OPTIONAL.
+
+The `nbf` claim works similarly to the `exp` claim above.
+
+.. code-block:: python
+
+ jwt.encode({'nbf': 1371720939}, 'secret')
+ jwt.encode({'nbf': datetime.utcnow()}, 'secret')
+
+Issuer Claim (iss)
+~~~~~~~~~~~~~~~~~~
+
+ The "iss" (issuer) claim identifies the principal that issued the
+ JWT. The processing of this claim is generally application specific.
+ The "iss" value is a case-sensitive string containing a StringOrURI
+ value. Use of this claim is OPTIONAL.
+
+.. code-block:: python
+
+ payload = {
+ 'some': 'payload',
+ 'iss': 'urn:foo'
+ }
+
+ token = jwt.encode(payload, 'secret')
+ decoded = jwt.decode(token, 'secret', issuer='urn:foo')
+
+If the issuer claim is incorrect, `jwt.InvalidIssuerError` will be raised.
+
+Audience Claim (aud)
+~~~~~~~~~~~~~~~~~~~~
+
+ The "aud" (audience) claim identifies the recipients that the JWT is
+ intended for. Each principal intended to process the JWT MUST
+ identify itself with a value in the audience claim. If the principal
+ processing the claim does not identify itself with a value in the
+ "aud" claim when this claim is present, then the JWT MUST be
+ rejected. In the general case, the "aud" value is an array of case-
+ sensitive strings, each containing a StringOrURI value. In the
+ special case when the JWT has one audience, the "aud" value MAY be a
+ single case-sensitive string containing a StringOrURI value. The
+ interpretation of audience values is generally application specific.
+ Use of this claim is OPTIONAL.
+
+.. code-block:: python
+
+ payload = {
+ 'some': 'payload',
+ 'aud': 'urn:foo'
+ }
+
+ token = jwt.encode(payload, 'secret')
+ decoded = jwt.decode(token, 'secret', audience='urn:foo')
+
+If the audience claim is incorrect, `jwt.InvalidAudienceError` will be raised.
+
+Issued At Claim (iat)
+~~~~~~~~~~~~~~~~~~~~~
+
+ The iat (issued at) claim identifies the time at which the JWT was issued.
+ This claim can be used to determine the age of the JWT. Its value MUST be a
+ number containing a NumericDate value. Use of this claim is OPTIONAL.
+
+If the `iat` claim is in the future, an `jwt.InvalidIssuedAtError` exception
+will be raised.
+
+.. code-block:: python
+
+ jwt.encode({'iat': 1371720939}, 'secret')
+ jwt.encode({'iat': datetime.utcnow()}, 'secret')
diff --git a/setup.cfg b/setup.cfg
index b435699..e468d36 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,6 @@
[flake8]
max-line-length = 119
+exclude = docs/
[wheel]
universal = 1