JwtEncoder 21.8.30
JWT authentication plugin for Bottle
Requires Python: >=3.6
Keywords: 'jwt oidc jwks'


## JwtEncoder

**JwtEncoder** provides abstracted JWT token generation (using [PyJWT](https://pypi.org/project/PyJWT/) and validation with a dictionary configuration ment to simply creation and deployment.

### Installation

```bash
# pip install JwtEncoder
```
### Usage
> Token creator:
```python
from JwtEncoder import JwtEncoder
from config import jwt_config

jenc = JwtEncoder(jwt_config)

tok = jenc.encode({'hello': 'world', 'aud': 'xyz'})
```
> Token consumer:
```python
from JwtEncoder import JwtEncoder
from config import jwt_config

jenc = JwtEncoder(jwt_config)

payload = jenc.decode(tok, audience='xyz')

print(payload['hello'])  # 'world'

```
#### Signature
```python
jenc = JwtEncoder(jwt_config=config)
```
**`jwt_config`** A python `dict` used to configure a JWT Authorizor.
* The makeup of this dict is discussed below in more detail. 
* By default a symmetric key is generated and tokens are signed using the **HS256** signing algorithm.

#### JwtEncoder Methods

**`tok = jenc.encode(payload, **kwargs)`** 

* Returns a JWT token `tok` from the dict `payload` with a signature and options described in `jwt_config`.  
* Additional options (kwargs) can be included as specified in the [PyJWT documentation](https://pyjwt.readthedocs.io/en/stable/)

**`pay = jenc.decode(tok, **kwargs)`**

* Validates the JWT token `tok` and returns decoded payload `pay` using the signature algorithm, signing keys, and issuer specified in `jwt_config`. 
* Additional options (kwargs) can be provided as specified in [PyJWT documentation](https://pyjwt.readthedocs.io/en/stable/)

**`unverpay = jenc.decode_noverify(tok)`**

* Returns contents of the payload `unverpay`, without verifying the signature or other jwt elements.
* The token should be verified with `jenc.decode()` before any contents are used for authorization or authentication.

**`hdrs = jenc.header(tok)`**
* Returns JWT header options.
* The tok should be validated first with `jenc.decode()`

## *jwt_config* configuration details

**JwtEncoder** uses the **PyJWT** module to implement both encoding and decoding of JWT's. The **`jwt_config`** python *`dict`* permits **JwtEncoder**  to support a various different JWT signing methods. These include:
* shared key signing (symmetric key)
* public and private key (asymmetric key)
* providing an X509 RSA certificate (asymmetric key)
* JWKS url (asymmetric key)

The choice determines which options and which signing algorithms are valid.
#### shared key (symmetric key)

To use a symmetric key **`jwt_config`** is of the form:
```python
jwt_config = {
    'key': 'mysecretkey',
    'alg': 'HS256'  # any valid JWT HS type
    'iss': 'urn:myissuer',
    'ttl': 3600 
}
```
* **`key`** the value of this is the shared binary signing key, and `key` indicates this JwtEncoder will use symmetric key signing.
* **`alg`** specifies the algorithm. For a symmentric key the default signing algorithm is **HS256**.

#### public/private key (asymmetric key)

**`pubkey`** and **`privkey`** (either or both) in **jwt_config*** indicates the JwtEncoder will use asymmetric keys for signing:
```python
jwt_config = {
    'pubkey': b'------BEGIN PUBLIC KEY....',
    'privkey': b'------BEGIN PRIVATE KEY...',
    'alg': 'RS256',
    'iss': 'urn:myissuer'
}
```
* **`pubkey`** and **`privkey`** are binary, PEM encoded keys. 
* Asymmetric signing can include RSA or elliptic curve key pairs (with the appropriate `alg` choice.)
* The default algorithm (`alg`) is **RS256** unless specified otherwise.
* If only the `pubkey` is provided the JwtEncoder can only decode tokens.
* If only the `privkey` is provided the JwtEncoder can only encode tokens.

#### X509 certificate (asymmetric key)
A binary X509 certificate indicates the JwtEncoder will use asymmetric RSA signing keys:
```python
jwt_config = {
    'cert' : b`----BEGIN CERTIFICATE---....',
    'alg': 'RS256',
    'iss': 'urn:xyz'
}
```
* `cert` provides only a public key, and hence  only token decoding - unless a `privkey` is also provided in the `jwt_config`.
* No validation of the X509 certificate authenticity is made.
* The default `alg` is **RS256**

#### JWKS retrieval
Retrieves JASON Web Key Sets (JWKS) signing keys from the specified URL:
```python
jst_config = {
    'jwks_url': 'https://.....',
    'iss': 'api://myapi'
}
```
* This retrieves JWKS keys (for example, from an OIDC provider)  The retrival specifies the algorithms used.
* Any number of keys can be provided by the jwks_url and the decoder will use the algorithm and signing key identifier (`kid`) specified in the jwt header. 
* You can only decode tokens using the `jwks_url` method.

### Additional Options

**`alg`** - *algorithm*

* `alg` specifies the signing algorithm used.
* For symmetric signing the default is `HS256`; for asymmetric signing, `RS256`
* There are various signing algorithms outlined in the JWT specifications, and in the PyJWT documentation.

**`ttl`** - *time to live*

* *time to live* is the length of time the tokens the JwtEncoder generates will be valid.
* If `ttl` is not specified, 3600 Seconds is used by default.
* The token `exp` value is set to the current epoch time + ttl; the token will also include `nbf` and `iat` set to the current time.
* If `ttl` is explicitly set to `None`, the `iat`, `nbf`, and `exp` elements are not added to token encoding - the tokens do not expire.
* Decoding an expired token raises an exception.

**`iss`** - *issuer*

* The issuer is added as `iss` to the payload for encoding tokens, and is verified when tokens are decoded.  
* An alternate issuer can be specified by adding `iss` to the payload before calling `encode({'iss': 'xyz'})`, and can be verified by passing the kwarg `issuer` on decode (e.g. `decode(token,issuer='xyz'))`
* decoding a token where the token `iss` doesn't match the issuer generates an exception.

### Audience Validation

If a jwt has an `aud` (audience) element specified when encoded, then `audience` kwarg must be specified when decoding for the token to validate.
```python
tok = jenc.encode({'aud': 'db21', 'scp': ['table.write']})
...
pay = jenc.decode(tok, audience=['db21', 'db22'])
```
* `audience` can be a `list`, as in the example, containing multiple options.  The token need only match one of them.
* decoding a token without the matching audience generates an exception.

### Generating Signing Keys

For reference only:

Generating Eliptic Curve key pairs using Openssl:
```bash
# openssl ecparam -name prime256v1 -genkey -noout -out private.pem
# openssl ec -in private.pem -pubout -out public.pem
```

Generating RSA key pairs using Openssl:
```bash
# openssl genrsa  -out rsaprivate.pem 2048
# openssl rsa -in rsaprivate.pem -pubout -out rsapublic.pem
```