Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jcberquist committed Aug 16, 2019
0 parents commit 448d159
Show file tree
Hide file tree
Showing 36 changed files with 1,648 additions and 0 deletions.
51 changes: 51 additions & 0 deletions .cfformat.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"array.empty_padding": true,
"array.multiline.element_count": 4,
"array.multiline.leading_comma": false,
"array.multiline.leading_comma.padding": true,
"array.multiline.min_length": 80,
"array.padding": true,
"binary_operators.padding": true,
"brackets.padding": true,
"comment.asterisks": "indent",
"for_loop_semicolons.padding": true,
"function_anonymous.empty_padding": false,
"function_anonymous.group_to_block_spacing": "spaced",
"function_anonymous.multiline.element_count": 4,
"function_anonymous.multiline.leading_comma": false,
"function_anonymous.multiline.leading_comma.padding": true,
"function_anonymous.multiline.min_length": 40,
"function_anonymous.padding": true,
"function_call.empty_padding": false,
"function_call.multiline.element_count": 4,
"function_call.multiline.leading_comma": false,
"function_call.multiline.leading_comma.padding": true,
"function_call.multiline.min_length": 40,
"function_call.padding": true,
"function_declaration.empty_padding": false,
"function_declaration.group_to_block_spacing": "spaced",
"function_declaration.multiline.element_count": 4,
"function_declaration.multiline.leading_comma": false,
"function_declaration.multiline.leading_comma.padding": true,
"function_declaration.multiline.min_length": 40,
"function_declaration.padding": true,
"indent_size": 4,
"keywords.block_to_keyword_spacing": "spaced",
"keywords.empty_group_spacing": false,
"keywords.group_to_block_spacing": "spaced",
"keywords.padding_inside_group": true,
"keywords.spacing_to_block": "spaced",
"keywords.spacing_to_group": true,
"max_columns": 120,
"parentheses.padding": true,
"strings.attributes.quote": "double",
"strings.quote": "single",
"struct.empty_padding": true,
"struct.multiline.element_count": 0,
"struct.multiline.leading_comma": false,
"struct.multiline.leading_comma.padding": true,
"struct.multiline.min_length": 0,
"struct.padding": true,
"struct.separator": ": ",
"tab_indent": false
}
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testbox/
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2019

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
10 changes: 10 additions & 0 deletions ModuleConfig.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
component {

this.title = 'jwt-cfml';
this.author = 'John Berquist';
this.webURL = 'https://github.com/jcberquist/jwt-cfml';
this.description = 'This module supports encoding and decoding JSON Web Tokens.';

function configure() {}

}
227 changes: 227 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# jwt-cfml

**jwt-cfml** is a CFML (Lucee and ColdFusion) library for encoding and decoding JSON Web Tokens.

It supports the following algorithms:

- HS256
- HS384
- HS512
- RS256
- RS384
- RS512
- ES256
- ES384
- ES512

In the case of the `RS` and `ES` algorithms, asymmetric keys are expected to be provided in unencrypted PEM or JWK format (in the latter case first deserialize the JWK to a CFML struct). When use PEM, private keys need to be encoded in PKCS#8 format.

If your private key is not currently in this format, conversion should be straightforward:

```bash
$ openssl pkcs8 -topk8 -nocrypt -in privatekey.pem -out privatekey.pk8
```

When decoding tokens, either a public key or certificate can be provided. (If a certificate is provided, the public key will be extracted from it.)

*You can pre-parse your encoded keys and pass the returned Java classes to the `encode()` and `decode()` methods, to avoid having them parsed on every method call. See [Parsing Asymmetric Keys](https://github.com/jcberquist/jwt-cfml-dev/blob/master/README.md#parsing-asymmetric-keys) below.*

## Installation

Installation is done via CommandBox:

```bash
$ box install jwt-cfml
```

`jwt-cfml` will be installed into a `jwtcfml` package directory by default.

*Alternatively the git repository can be cloned into the desired directory.*

### Standalone

Once the library has been installed, the core `jwt` component can be instantiated directly:

```cfc
jwt = new path.to.jwtcfml.models.jwt();
```

### ColdBox Module

You can make use of the library via the injection DSL: `jwt@jwtcfml`

## Usage

### Encoding tokens:

```cfc
payload = {'key': 'value'};
secret = 'secret';
token = jwt.encode(payload, secret, 'HS256');
```

```cfc
pemPrivateKey = '
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
';
token = jwt.encode(payload, pemPrivateKey, 'RS256');
```

```cfc
jwk = {
"alg": "RS256",
"d": "...",
"dp": "...",
"dq": "...",
"e": "AQAB",
"kty": "RSA",
"n": "...",
"p": "...",
"q": "...",
"qi": "..."
};
token = jwt.encode(payload, jwk, 'RS256');
```

When a token is encoded, a header is automatically included containing
`"typ"` set to `"JWT"` and `"alg"` set to the passed in algorithm. If you
need to add additional headers a fourth argument, `headers`, is available
for this:

```cfc
token = jwt.encode(payload, pemPrivateKey, 'RS256', {'kid': 'abc123'});
```

If your token payload contains `"iat"`, `"exp"`, or `"nbf"` claims, you can
set these to CFML date objects, and they will automatically be converted to
UNIX timestamps in the generated token for you.

```cfc
payload = {'iat': now()};
token = jwt.encode(payload, secret, 'HS256');
```

### Decoding tokens:

```cfc
token = 'eyJ0e...';
secret = 'secret';
payload = jwt.decode(token, secret, 'HS256');
```

```cfc
token = 'eyJ0e...';
pemPublicKey = '
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
';
payload = jwt.decode(token, pemPublicKey, 'RS256');
```

```cfc
token = 'eyJ0e...';
pemCertificate = '
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
';
payload = jwt.decode(token, pemCertificate, 'RS256');
```

```cfc
token = 'eyJ0e...';
jwk = {
"e": "AQAB",
"kty": "RSA",
"alg": "RS256",
"n": "...",
"use": "sig"
}
payload = jwt.decode(token, jwk, 'RS256');
```

*Note: This library does not rely solely on the algorithm specified in the token header. You **must** specify the allowed algorithms (either as a string or an array) when calling `decode()`. The algorithm in the token header must match one of the allowed algorithms.*

If the decoded payload contains `"iat"`, `"exp"`, or `"nbf"` claims, they will be automatically converted from UNIX timestamps to CFML date objects for you.

#### Getting the token header

If you need to get the token header before decoding (e.g. you need a `"kid"` from it), you can use the `jwt.getHeader()` method. This will return the token header as a struct.

```cfc
token = 'eyJ0e...';
header = jwt.getHeader(token);
```

#### Token validity

If a token signature is invalid, the `jwt.decode()` method will throw an error. Further, if the payload contains a `"exp"` or `"nbf"` claim these will be verified as well.

If you also wish to verify an audience or issuer claim, you can pass valid claims into the decode method:

```cfc
claims = {
"iss": "somissuer",
"aud": "someaudience" // this can also be an array
};
payload = jwt.decode(token, pemCertificate, 'RS256', claims);
```

This argument can also be used to ignore the `"exp"` and `"nbf"` claims or to validate them against a timestamp other than the current time:

```cfc
claims = {
// `exp` will be validated against 1 min in the past instead of the current time
"exp": dateAdd('n', -1, now()),
// `nbf` will be ignored
"nbf": false
};
payload = jwt.decode(token, pemCertificate, 'RS256', claims);
```

#### Unverified Payload

If you need to get the payload without doing any verification at all you can pass `verify=false` into the decode method:

```cfc
jwt.decode(token = token, verify = false);
```

### Parsing Asymmetric Keys

Every time a PEM key or JWK is passed into `encode()` and `decode()` it must be converted to binary data and then the appropriate Java class created. You can avoid this (minor) overhead by parsing your key upfront, and then passing the generated Java key class directly into `encode()` and `decode()`:

```cfc
pemCertificate = '
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
';
publicKey = jwt.parsePEMEncodedKey(pemCertificate);
payload = jwt.decode(token, publicKey, 'RS256');
```

```cfc
jwk = {
"e": "AQAB",
"kty": "RSA",
"alg": "RS256",
"n": "AN...",
"use": "sig"
};
publicKey = jwt.parseJWK(jwk);
payload = jwt.decode(token, publicKey, 'RS256');
```

### Acknowledgments

- <https://github.com/jpadilla/pyjwt>
- <https://github.com/jwtk/jjwt>
- <https://github.com/apache/cxf>
- <https://bitbucket.org/b_c/jose4j>
43 changes: 43 additions & 0 deletions box.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name":"JWT CFML",
"version":"1.0.0",
"author":"John Berquist",
"location":"forgeboxStorage",
"homepage":"https://github.com/jcberquist/jwt-cfml",
"documentation":"",
"repository":{
"type":"",
"URL":""
},
"bugs":"",
"slug":"jwt-cfml",
"packageDirectory": "jwtcfml",
"shortDescription":"JWT CFML is a CFML (Lucee and ColdFusion) library for using JSON Web Tokens.",
"description":"",
"instructions":"",
"changelog":"",
"type":"modules",
"keywords":[
"jwt"
],
"private":false,
"projectURL":"",
"license":[
{
"type":"MIT",
"URL":""
}
],
"contributors":[],
"dependencies":{},
"devDependencies":{
"testbox":"^3.0.0"
},
"installPaths":{
"testbox":"testbox/"
},
"scripts": {
"tests": "start tests/server.json && start tests/server-2018.json && start tests/server-2016.json",
"format": "cfformat models,tests --overwrite"
}
}
Loading

0 comments on commit 448d159

Please sign in to comment.