├── .gitignore ├── .travis.yml ├── AUTHORS.md ├── LICENSE ├── README.md ├── ci ├── dist.ini ├── examples ├── README.md ├── guard.lua └── redjwt.lua ├── lib └── resty │ ├── evp.lua │ ├── jwt-validators.lua │ └── jwt.lua ├── lua-resty-jwt-dev-0.rockspec ├── t ├── function-secret.t ├── load-verify-jwe.t ├── load-verify.t ├── sign-verify.t ├── validate-jwt.issuer.t ├── validate-jwt.lifecycle.t ├── validate-jwt.t ├── validators.t ├── verify.claims.issuer.t └── verify.claims.lifecycle.t ├── testcerts ├── README.md ├── cert.pem ├── privatekey.pem ├── pubkey.pem └── root.pem └── vendor ├── README.md └── resty └── hmac.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *swp 2 | t/servroot 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | 3 | services: 4 | - docker 5 | 6 | before_install: 7 | - ./ci before_install 8 | 9 | script: 10 | - ./ci script 11 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | Thank you guys for making this project better! 3 | 4 | ## Repo Owners 5 | - [@SkyLothar](https://github.com/skylothar) 6 | 7 | ## Tons of Features 8 | - Em. [@fermaem](https://github.com/fermaem) 9 | - Nathan Toone [@toonetown](https://github.com/toonetown) 10 | 11 | ## Resty.EVP 12 | - Daniel Hiltgen [@dhiltgen](https://github.com/dhiltgen) 13 | - ravenscar [@ravenscar](https://github.com/ravenscar) 14 | - William [@Deadleg](https://github.com/Deadleg) 15 | 16 | ## Patches and Suggestions 17 | - Daniel Hiltgen [@dhiltgen](https://github.com/dhiltgen) 18 | - Jun Hanamaki [@junhanamaki](https://github.com/junhanamaki) 19 | 20 | ## Resty.HMAC 21 | - @jkeys089 [lua-resty-hmac](https://github.com/jkeys089/lua-resty-hmac) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-jwt - [JWT](http://self-issued.info/docs/draft-jones-json-web-token-01.html) for ngx_lua and LuaJIT 5 | 6 | [![Build Status](https://img.shields.io/travis/SkyLothar/lua-resty-jwt.svg?style=flat-square)](https://travis-ci.org/SkyLothar/lua-resty-jwt) 7 | 8 | 9 | **Attention :exclamation: the hmac lib used here is [lua-resty-hmac](https://github.com/jkeys089/lua-resty-hmac), not the one in luarocks.** 10 | 11 | Installation 12 | ============ 13 | - opm: `opm get SkyLothar/lua-resty-jwt` 14 | - luarocks: `luarocks install lua-resty-jwt` 15 | - Head to [release page](https://github.com/SkyLothar/lua-resty-jwt/releases) and download `tar.gz` 16 | 17 | version 18 | ======= 19 | 20 | 0.1.10 21 | 22 | 23 | Table of Contents 24 | ================= 25 | 26 | * [Name](#name) 27 | * [Status](#status) 28 | * [Description](#description) 29 | * [Synopsis](#synopsis) 30 | * [Methods](#methods) 31 | * [sign](#sign) 32 | * [verify](#verify) 33 | * [load and verify](#load--verify) 34 | * [sign JWE](#sign-jwe) 35 | * [Verification](#verification) 36 | * [JWT Validators](#jwt-validators) 37 | * [Legacy/Timeframe options](#legacy-timeframe-options) 38 | * [Example](#examples) 39 | * [Installation](#installation) 40 | * [Testing With Docker](#testing-with-docker) 41 | * [Authors](AUTHORS.md) 42 | * [See Also](#see-also) 43 | 44 | Status 45 | ====== 46 | 47 | This library is under active development but is considered production ready. 48 | 49 | Description 50 | =========== 51 | 52 | This library requires an nginx build with OpenSSL, 53 | the [ngx_lua module](http://wiki.nginx.org/HttpLuaModule), 54 | the [LuaJIT 2.0](http://luajit.org/luajit.html), 55 | the [lua-resty-hmac](https://github.com/jkeys089/lua-resty-hmac), 56 | and the [lua-resty-string](https://github.com/openresty/lua-resty-string), 57 | 58 | 59 | Synopsis 60 | ======== 61 | 62 | ```lua 63 | # nginx.conf: 64 | 65 | lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;"; 66 | 67 | server { 68 | default_type text/plain; 69 | location = /verify { 70 | content_by_lua ' 71 | local cjson = require "cjson" 72 | local jwt = require "resty.jwt" 73 | 74 | local jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 75 | ".eyJmb28iOiJiYXIifQ" .. 76 | ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY" 77 | local jwt_obj = jwt:verify("lua-resty-jwt", jwt_token) 78 | ngx.say(cjson.encode(jwt_obj)) 79 | '; 80 | } 81 | location = /sign { 82 | content_by_lua ' 83 | local cjson = require "cjson" 84 | local jwt = require "resty.jwt" 85 | 86 | local jwt_token = jwt:sign( 87 | "lua-resty-jwt", 88 | { 89 | header={typ="JWT", alg="HS256"}, 90 | payload={foo="bar"} 91 | } 92 | ) 93 | ngx.say(jwt_token) 94 | '; 95 | } 96 | } 97 | ``` 98 | 99 | [Back to TOC](#table-of-contents) 100 | 101 | Methods 102 | ======= 103 | 104 | To load this library, 105 | 106 | 1. you need to specify this library's path in ngx_lua's [lua_package_path](https://github.com/openresty/lua-nginx-module#lua_package_path) directive. For example, `lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;";`. 107 | 2. you use `require` to load the library into a local Lua variable: 108 | 109 | ```lua 110 | local jwt = require "resty.jwt" 111 | ``` 112 | 113 | [Back to TOC](#table-of-contents) 114 | 115 | 116 | sign 117 | ---- 118 | 119 | `syntax: local jwt_token = jwt:sign(key, table_of_jwt)` 120 | 121 | sign a table_of_jwt to a jwt_token. 122 | 123 | The `alg` argument specifies which hashing algorithm to use (`HS256`, `HS512`, `RS256`). 124 | 125 | ### sample of table_of_jwt ### 126 | ``` 127 | { 128 | "header": {"typ": "JWT", "alg": "HS512"}, 129 | "payload": {"foo": "bar"} 130 | } 131 | ``` 132 | 133 | verify 134 | ------ 135 | `syntax: local jwt_obj = jwt:verify(key, jwt_token [, claim_spec [, ...]])` 136 | 137 | verify a jwt_token and returns a jwt_obj table. `key` can be a pre-shared key (as a string), *or* a function which takes a single parameter (the value of `kid` from the header) and returns either the pre-shared key (as a string) for the `kid` or `nil` if the `kid` lookup failed. This call will fail if you try to specify a function for `key` and there is no `kid` existing in the header. 138 | 139 | See [Verification](#verification) for details on the format of `claim_spec` parameters. 140 | 141 | 142 | load & verify 143 | ------------- 144 | ``` 145 | syntax: local jwt_obj = jwt:load_jwt(jwt_token) 146 | syntax: local verified = jwt:verify_jwt_obj(key, jwt_obj [, claim_spec [, ...]]) 147 | ``` 148 | 149 | 150 | __verify = load_jwt + verify_jwt_obj__ 151 | 152 | load jwt, check for kid, then verify it with the correct key 153 | 154 | 155 | ### sample of jwt_obj ### 156 | ``` 157 | { 158 | "raw_header": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9", 159 | "raw_payload: "eyJmb28iOiJiYXIifQ", 160 | "signature": "wrong-signature", 161 | "header": {"typ": "JWT", "alg": "HS256"}, 162 | "payload": {"foo": "bar"}, 163 | "verified": false, 164 | "valid": true, 165 | "reason": "signature mismatched: wrong-signature" 166 | } 167 | ``` 168 | 169 | sign-jwe 170 | -------- 171 | 172 | `syntax: local jwt_token = jwt:sign(key, table_of_jwt)` 173 | 174 | sign a table_of_jwt to a jwt_token. 175 | 176 | The `alg` argument specifies which hashing algorithm to use for encrypting key (`dir`). 177 | The `enc` argument specifies which hashing algorithm to use for encrypting payload (`A128CBC-HS256`, `A256CBC-HS512`) 178 | 179 | ### sample of table_of_jwt ### 180 | ``` 181 | { 182 | "header": {"typ": "JWE", "alg": "dir", "enc":"A128CBC-HS256"}, 183 | "payload": {"foo": "bar"} 184 | } 185 | ``` 186 | 187 | [Back to TOC](#table-of-contents) 188 | 189 | 190 | Verification 191 | ============ 192 | 193 | Both the `jwt:load` and `jwt:verify_jwt_obj` functions take, as additional parameters, any number of optional `claim_spec` parameters. A `claim_spec` is simply a lua table of claims and validators. Each key in the `claim_spec` table corresponds to a matching key in the payload, and the `validator` is a function that will be called to determine if the claims are met. 194 | 195 | The signature of a `validator` function is: 196 | 197 | ``` 198 | function(val, claim, jwt_json) 199 | ``` 200 | 201 | Where `val` is the value of the claim from the `jwt_obj` being tested (or nil if it doesn't exist in the object's payload), `claim` is the name of the claim that is being verified, and `jwt_json` is a json-serialized representation of the object that is being verified. If the function has no need of the `claim` or `jwt_json`, parameters, they may be left off. 202 | 203 | A `validator` function returns either `true` or `false`. Any `validator` *MAY* raise an error, and the validation will be treated as a failure, and the error that was raised will be put into the reason field of the resulting object. If a `validator` returns nothing (i.e. `nil`), then the function is treated to have succeeded - under the assumption that it would have raised an error if it would have failed. 204 | 205 | A special claim named `__jwt` can be used such that if a `validator` function exists for it, then the `validator` will be called with a deep clone of the entire parsed jwt object as the value of `val`. This is so that you can write verifications for an entire object that may depend on one or more claims. 206 | 207 | Multiple `claim_spec` tables can be specified to the `jwt:load` and `jwt:verify_jwt_obj` - and they will be executed in order. There is no guarantee of the execution order of individual `validators` within a single `claim_spec`. If a `claim_spec` fails, then any following `claim_specs` will *NOT* be executed. 208 | 209 | 210 | ### sample `claim_spec` ### 211 | ``` 212 | { 213 | sub = function(val) return string.match("^[a-z]+$", val) end, 214 | iss = function(val) 215 | for _, value in pairs({ "first", "second" }) do 216 | if value == val then return true end 217 | end 218 | return false 219 | end, 220 | __jwt = function(val, claim, jwt_json) 221 | if val.payload.foo == nil or val.payload.bar == nil then 222 | error("Need to specify either 'foo' or 'bar'") 223 | end 224 | end 225 | } 226 | ``` 227 | 228 | JWT Validators 229 | -------------- 230 | 231 | A library of helpful `validator` functions exists at `resty.jwt-validators`. You can use this library by including: 232 | ``` 233 | local validators = require "resty.jwt-validators" 234 | ``` 235 | 236 | The following functions are currently defined in the validator library. Those marked with "(opt)" means that the same function exists named `opt_` which takes the same parameters. The "opt" version of the function will return `true` if the key does not exist in the payload of the jwt_object being verified, while the "non-opt" version of the function will return false if the key does not exist in the payload of the jwt_object being verified. 237 | 238 | #### `validators.chain(...)` #### 239 | Returns a validator that chains the given functions together, one after another - as long as they keep passing their checks. 240 | 241 | #### `validators.required(chain_function)` #### 242 | Returns a validator that returns `false` if a value doesn't exist. If the value exists and a `chain_function` is specified, then the value of `chain_function(val, claim, jwt_json)` will be returned, otherwise, `true` will be returned. This allows for specifying that a value is both required *and* it must match some additional check. 243 | 244 | #### `validators.require_one_of(claim_keys)` #### 245 | Returns a validator which errors with a message if *NONE* of the given claim keys exist. It is expected that this function is used against a full jwt object. The claim_keys must be a non-empty table of strings. 246 | 247 | #### `validators.check(check_val, check_function, name, check_type)` (opt) #### 248 | Returns a validator that checks if the result of calling the given `check_function` for the tested value and `check_val` returns true. The value of `check_val` and `check_function` cannot be nil. The optional `name` is used for error messages and defaults to "check_value". The optional `check_type` is used to make sure that the check type matches and defaults to `type(check_val)`. The first parameter passed to check_function will *never* be nil. If the `check_function` raises an error, that will be appended to the error message. 249 | 250 | #### `validators.equals(check_val)` (opt) #### 251 | Returns a validator that checks if a value exactly equals (using `==`) the given check_value. The value of `check_val` cannot be nil. 252 | 253 | #### `validators.matches(pattern)` (opt) #### 254 | Returns a validator that checks if a value matches the given pattern (using `string.match`). The value of `pattern` must be a string. 255 | 256 | #### `validators.any_of(check_values, check_function, name, check_type, table_type)` (opt) #### 257 | Returns a validator which calls the given `check_function` for each of the given `check_values` and the tested value. If any of these calls return `true`, then this function returns `true`. The value of `check_values` must be a non-empty table with all the same types, and the value of `check_function` must not be `nil`. The optional `name` is used for error messages and defaults to "check_values". The optional `check_type` is used to make sure that the check type matches and defaults to `type(check_values[1])` - the table type. 258 | 259 | #### `validators.equals_any_of(check_values)` (opt) #### 260 | Returns a validator that checks if a value exactly equals any of the given `check_values`. 261 | 262 | #### `validators.matches_any_of(patterns)` (opt) #### 263 | Returns a validator that checks if a value matches any of the given `patterns`. 264 | 265 | #### `validators.contains_any_of(check_values,name)` (opt) #### 266 | Returns a validator that checks if a value of expected type `string` exists in any of the given `check_values`. The value of `check_values`must be a non-empty table with all the same types. The optional name is used for error messages and defaults to `check_values`. 267 | 268 | #### `validators.greater_than(check_val)` (opt) #### 269 | Returns a validator that checks how a value compares (numerically, using `>`) to a given `check_value`. The value of `check_val` cannot be `nil` and must be a number. 270 | 271 | #### `validators.greater_than_or_equal(check_val)` (opt) #### 272 | Returns a validator that checks how a value compares (numerically, using `>=`) to a given `check_value`. The value of `check_val` cannot be `nil` and must be a number. 273 | 274 | #### `validators.less_than(check_val)` (opt) #### 275 | Returns a validator that checks how a value compares (numerically, using `<`) to a given `check_value`. The value of `check_val` cannot be `nil` and must be a number. 276 | 277 | #### `validators.less_than_or_equal(check_val)` (opt) #### 278 | Returns a validator that checks how a value compares (numerically, using `<=`) to a given `check_value`. The value of `check_val` cannot be `nil` and must be a number. 279 | 280 | #### `validators.is_not_before()` (opt) #### 281 | Returns a validator that checks if the current time is not before the tested value within the system's leeway. This means that: 282 | ``` 283 | val <= (system_clock() + system_leeway). 284 | ``` 285 | 286 | #### `validators.is_not_expired()` (opt) #### 287 | Returns a validator that checks if the current time is not equal to or after the tested value within the system's leeway. This means that: 288 | ``` 289 | val > (system_clock() - system_leeway). 290 | ``` 291 | 292 | #### `validators.is_at()` (opt) #### 293 | Returns a validator that checks if the current time is the same as the tested value within the system's leeway. This means that: 294 | ``` 295 | val >= (system_clock() - system_leeway) and val <= (system_clock() + system_leeway). 296 | ``` 297 | 298 | #### `validators.set_system_leeway(leeway)` #### 299 | A function to set the leeway (in seconds) used for `is_not_before` and `is_not_expired`. The default is to use `0` seconds 300 | 301 | #### `validators.set_system_clock(clock)` #### 302 | A function to set the system clock used for `is_not_before` and `is_not_expired`. The default is to use `ngx.now` 303 | 304 | ### sample `claim_spec` using validators ### 305 | ``` 306 | local validators = require "resty.jwt-validators" 307 | local claim_spec = { 308 | sub = validators.opt_matches("^[a-z]+$), 309 | iss = validators.equals_any_of({ "first", "second" }), 310 | __jwt = validators.require_one_of({ "foo", "bar" }) 311 | } 312 | ``` 313 | 314 | 315 | Legacy/Timeframe options 316 | ------------------------ 317 | 318 | In order to support code which used previous versions of this library, as well as to simplify specifying timeframe-based `claim_specs`, you may use in place of any single `claim_spec` parameter a table of `validation_options`. The parameter should be expressed as a key/value table. Each key of the table should be picked from the following list. 319 | 320 | When using legacy `validation_options`, you *MUST ONLY* specify these options. That is, you cannot mix legacy `validation_options` with other `claim_spec` validators. In order to achieve that, you must specify multiple options to the `jwt:load`/`jwt:verify_jwt_obj` functions. 321 | 322 | * `lifetime_grace_period`: Define the leeway in seconds to account for clock skew between the server that generated the jwt and the server validating it. Value should be zero (`0`) or a positive integer. 323 | 324 | * When this validation option is specified, the process will ensure that the jwt contains at least one of the two `nbf` or `exp` claim and compare the current clock time against those boundaries. Would the jwt be deemed as expired or not valid yet, verification will fail. 325 | 326 | * When none of the `nbf` and `exp` claims can be found, verification will fail. 327 | 328 | * `nbf` and `exp` claims are expected to be expressed in the jwt as numerical values. Wouldn't that be the case, verification will fail. 329 | 330 | * Specifying this option is equivalent to calling: 331 | ``` 332 | validators.set_system_leeway(leeway) 333 | ``` 334 | 335 | and specifying as a `claim_spec`: 336 | ``` 337 | { 338 | __jwt = validators.require_one_of({ "nbf", "exp" }), 339 | nbf = validators.opt_is_not_before(), 340 | exp = validators.opt_is_not_expired() 341 | } 342 | ``` 343 | 344 | * `require_nbf_claim`: Express if the `nbf` claim is optional or not. Value should be a boolean. 345 | 346 | * When this validation option is set to `true` and no `lifetime_grace_period` has been specified, a zero (`0`) leeway is implied. 347 | 348 | * Specifying this option is equivalent to specifying as a `claim_spec`: 349 | ``` 350 | { 351 | nbf = validators.is_not_before(), 352 | } 353 | ``` 354 | 355 | * `require_exp_claim`: Express if the `exp` claim is optional or not. Value should be a boolean. 356 | 357 | * When this validation option is set to `true` and no `lifetime_grace_period` has been specified, a zero (`0`) leeway is implied. 358 | 359 | * Specifying this option is equivalent to specifying as a `claim_spec`: 360 | ``` 361 | { 362 | exp = validators.is_not_expired(), 363 | } 364 | ``` 365 | 366 | * `valid_issuers`: Whitelist the vetted issuers of the jwt. Value should be a array of strings. 367 | 368 | * When this validation option is specified, the process will compare the jwt `iss` claim against the list of valid issuers. Comparison is done in a case sensitive manner. Would the jwt issuer not be found in the whitelist, verification will fail. 369 | 370 | * `iss` claim is expected to be expressed in the jwt as a string. Wouldn't that be the case, verification will fail. 371 | 372 | * Specifying this option is equivalent to specifying as a `claim_spec`: 373 | ``` 374 | { 375 | iss = validators.equals_any_of(valid_issuers), 376 | } 377 | ``` 378 | 379 | 380 | ### sample of validation_options usage ### 381 | ``` 382 | local jwt_obj = jwt:verify(key, jwt_token, 383 | { 384 | lifetime_grace_period = 120, 385 | require_exp_claim = true, 386 | valid_issuers = { "my-trusted-issuer", "my-other-trusteed-issuer" } 387 | } 388 | ) 389 | ``` 390 | 391 | 392 | 393 | Examples 394 | ======== 395 | * [JWT Auth With Query and Cookie](examples/README.md#jwt-auth-using-query-and-cookie) 396 | * [JWT Auth With KID and Store Your Key in Redis](examples/README.md#jwt-auth-with-kid-and-store-keys-in-redis) 397 | 398 | [Back to TOC](#table-of-contents) 399 | 400 | 401 | Installation 402 | ============ 403 | 404 | Using Luarocks 405 | ```bash 406 | luarocks install lua-resty-jwt 407 | ``` 408 | 409 | It is recommended to use the latest [ngx_openresty bundle](http://openresty.org) directly. 410 | 411 | Also, You need to configure 412 | the [lua_package_path](https://github.com/openresty/lua-nginx-module#lua_package_path) directive to 413 | add the path of your lua-resty-jwt source tree to ngx_lua's Lua module search path, as in 414 | 415 | ```nginx 416 | # nginx.conf 417 | http { 418 | lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;"; 419 | ... 420 | } 421 | ``` 422 | 423 | and then load the library in Lua: 424 | 425 | ```lua 426 | local jwt = require "resty.jwt" 427 | ``` 428 | 429 | 430 | [Back to TOC](#table-of-contents) 431 | 432 | Testing With Docker 433 | =================== 434 | 435 | ``` 436 | ./ci script 437 | ``` 438 | 439 | [Back to TOC](#table-of-contents) 440 | 441 | 442 | See Also 443 | ======== 444 | * the ngx_lua module: http://wiki.nginx.org/HttpLuaModule 445 | 446 | [Back to TOC](#table-of-contents) 447 | -------------------------------------------------------------------------------- /ci: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ev # Ref https://docs.travis-ci.com/user/customizing-the-build/#Implementing-Complex-Build-Steps 4 | 5 | case "${1:?}" in 6 | before_install) 7 | docker pull skylothar/openresty-testsuite:latest 8 | ;; 9 | script) 10 | docker run \ 11 | -a stdin -a stdout -a stderr -i \ 12 | --rm \ 13 | --entrypoint="" \ 14 | -v "$(pwd)":/lua-resty-jwt -w /lua-resty-jwt \ 15 | --name lua-resty-jwt-tests \ 16 | skylothar/openresty-testsuite:latest \ 17 | /bin/sh -c 'luarocks make lua-resty-jwt-dev-0.rockspec && prove -r t' 18 | ;; 19 | esac 20 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-jwt 2 | abstract = JWT For The Great Openresty 3 | author = SkyLothar (skylothar) 4 | is_original = yes 5 | license = apache2 6 | lib_dir = lib 7 | doc_dir = lib 8 | repo_link = https://github.com/SkyLothar/lua-resty-jwt 9 | main_module = lib/resty/jwt.lua 10 | requires = luajit, jkeys089/lua-resty-hmac >= 0.01 11 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | How You Can Use lua-resty-jwt 2 | ============================= 3 | 4 | ### jwt auth using query and cookie 5 | 6 | nginx config 7 | ``` 8 | location / { 9 | access_log off; 10 | default_type text/plain; 11 | 12 | set $jwt_secret "your-own-jwt-secret"; 13 | access_by_lua_file /etc/nginx/lua/guard.lua; 14 | 15 | echo "i am protected by jwt guard"; 16 | } 17 | ``` 18 | [guard.lua](guard.lua) 19 | 20 | 21 | ### jwt auth with kid and store keys in redis 22 | nginx config 23 | ``` 24 | location / { 25 | set $redhost "127.0.0.1"; 26 | set $redport 6379; 27 | # set $reddb 1; 28 | # set $redauth "your-redis-pass"; 29 | access_by_lua_file /etc/nginx/lua/redjwt.lua; 30 | 31 | echo "i am protected jwt guard"; 32 | } 33 | ``` 34 | [redjwt.lua](redjwt.lua) 35 | -------------------------------------------------------------------------------- /examples/guard.lua: -------------------------------------------------------------------------------- 1 | local jwt = require "resty.jwt" 2 | local jwt_token = ngx.var.arg_jwt 3 | if jwt_token then 4 | ngx.header['Set-Cookie'] = "jwt=" .. jwt_token 5 | else 6 | jwt_token = ngx.var.cookie_jwt 7 | end 8 | 9 | local jwt_obj = jwt:verify(ngx.var.jwt_secret, jwt_token, 0) 10 | 11 | if not jwt_obj["verified"] then 12 | local site = ngx.var.scheme .. "://" .. ngx.var.http_host; 13 | local args = ngx.req.get_uri_args(); 14 | 15 | ngx.status = ngx.status = ngx.HTTP_UNAUTHORIZED 16 | ngx.say(jwt_obj.reason); 17 | ngx.exit(ngx.HTTP_OK) 18 | 19 | -- or you can redirect to your website to get a new jwt token 20 | -- then redirect back 21 | -- return ngx.redirect("http://your-site-host/get_jwt") 22 | end 23 | -------------------------------------------------------------------------------- /examples/redjwt.lua: -------------------------------------------------------------------------------- 1 | local function redkey(kid) 2 | -- get key from redis 3 | -- nil (something went wrong, let the request pass) 4 | -- null (no such key, reject the request) 5 | -- key (the key) 6 | 7 | local redis = require "resty.redis" 8 | local red = redis:new() 9 | red:set_timeout(100) -- 100ms 10 | 11 | local ok, err = red:connect(ngx.var.redhost, ngx.var.redport) 12 | if not ok then 13 | ngx.log(ngx.ERR, "failed to connect to redis: ", err) 14 | return nil 15 | end 16 | 17 | if ngx.var.redauth then 18 | local ok, err = red:auth(ngx.var.redauth) 19 | if not ok then 20 | ngx.log("failed to authenticate: ", err) 21 | return nil 22 | end 23 | end 24 | 25 | if ngx.var.reddb then 26 | local ok, err = red:select(ngx.var.reddb) 27 | if not ok then 28 | ngx.log("failed to select db: ", ngx.var.reddb, " ", err) 29 | return nil 30 | end 31 | end 32 | 33 | local res, err = red:get(kid) 34 | if not res then 35 | ngx.log(ngx.ERR, "failed to get kid: ", kid ,", ", err) 36 | return nil 37 | end 38 | 39 | if res == ngx.null then 40 | ngx.log(ngx.ERR, "key ", kid, " not found") 41 | return ngx.null 42 | end 43 | 44 | local ok, err = red:close() 45 | if not ok then 46 | ngx.log(ngx.ERR, "failed to close: ", err) 47 | end 48 | 49 | return res 50 | end 51 | 52 | 53 | local jwt = require "resty.jwt" 54 | 55 | local jwt_obj = jwt:load_jwt(ngx.var.arg_jwt) 56 | if not jwt_obj.valid then 57 | ngx.status = ngx.HTTP_BAD_REQUEST 58 | ngx.say("invalid jwt") 59 | ngx.exit(ngx.HTTP_OK) 60 | end 61 | local kid = jwt_obj.header.kid 62 | if kid == nil then 63 | ngx.status = ngx.HTTP_BAD_REQUEST 64 | ngx.say("missing kid") 65 | ngx.exit(ngx.HTTP_OK) 66 | end 67 | 68 | local jwt_key_dict= ngx.shared.jwt_key_dict 69 | local key = jwt_key_dict:get(kid) 70 | local flush = false 71 | if key == nil then 72 | -- key not found in cache, let's check if it's in redis 73 | -- new key found, if the new key is valid, older ones should be deleted 74 | key = redkey(kid) 75 | flush = true 76 | end 77 | 78 | if key == ngx.null then 79 | -- no such key 80 | ngx.status = ngx.HTTP_UNAUTHORIZED 81 | ngx.say("your kid: [", kid ,"] is not valid") 82 | ngx.exit(ngx.HTTP_OK) 83 | elseif key == nil then 84 | -- get key error 85 | ngx.say("something wrong with our server. I'll let you pass this time") 86 | else 87 | local verified = jwt:verify_jwt_obj(key, jwt_obj, 30) 88 | 89 | if not verified.verified then 90 | ngx.status = ngx.HTTP_UNAUTHORIZED 91 | ngx.say(jwt_obj.reason) 92 | ngx.exit(ngx.HTTP_OK) 93 | end 94 | 95 | if flush then 96 | -- flush all cached keys, if a new valid key showd up 97 | -- the older ones were expired 98 | jwt_key_dict:flush_all() 99 | jwt_key_dict:set(kid, key) 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/resty/evp.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) by Daniel Hiltgen (daniel.hiltgen@docker.com) 2 | 3 | 4 | local ffi = require "ffi" 5 | local _C = ffi.C 6 | local _M = { _VERSION = "0.0.2" } 7 | 8 | 9 | local CONST = { 10 | SHA256_DIGEST = "SHA256", 11 | SHA512_DIGEST = "SHA512", 12 | } 13 | _M.CONST = CONST 14 | 15 | 16 | -- Reference: https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying 17 | ffi.cdef[[ 18 | // Error handling 19 | unsigned long ERR_get_error(void); 20 | const char * ERR_reason_error_string(unsigned long e); 21 | 22 | // Basic IO 23 | typedef struct bio_st BIO; 24 | typedef struct bio_method_st BIO_METHOD; 25 | BIO_METHOD *BIO_s_mem(void); 26 | BIO * BIO_new(BIO_METHOD *type); 27 | int BIO_puts(BIO *bp,const char *buf); 28 | void BIO_vfree(BIO *a); 29 | int BIO_write(BIO *b, const void *buf, int len); 30 | 31 | // RSA 32 | typedef struct rsa_st RSA; 33 | int RSA_size(const RSA *rsa); 34 | void RSA_free(RSA *rsa); 35 | typedef int pem_password_cb(char *buf, int size, int rwflag, void *userdata); 36 | RSA * PEM_read_bio_RSAPrivateKey(BIO *bp, RSA **rsa, pem_password_cb *cb, 37 | void *u); 38 | RSA * PEM_read_bio_RSAPublicKey(BIO *bp, RSA **rsa, pem_password_cb *cb, 39 | void *u); 40 | 41 | // EVP PKEY 42 | typedef struct evp_pkey_st EVP_PKEY; 43 | typedef struct engine_st ENGINE; 44 | EVP_PKEY *EVP_PKEY_new(void); 45 | int EVP_PKEY_set1_RSA(EVP_PKEY *pkey,RSA *key); 46 | EVP_PKEY *EVP_PKEY_new_mac_key(int type, ENGINE *e, 47 | const unsigned char *key, int keylen); 48 | void EVP_PKEY_free(EVP_PKEY *key); 49 | int i2d_RSA(RSA *a, unsigned char **out); 50 | 51 | // PUBKEY 52 | EVP_PKEY *PEM_read_bio_PUBKEY(BIO *bp, EVP_PKEY **x, 53 | pem_password_cb *cb, void *u); 54 | 55 | // X509 56 | typedef struct x509_st X509; 57 | X509 *PEM_read_bio_X509(BIO *bp, X509 **x, pem_password_cb *cb, void *u); 58 | EVP_PKEY * X509_get_pubkey(X509 *x); 59 | void X509_free(X509 *a); 60 | void EVP_PKEY_free(EVP_PKEY *key); 61 | int i2d_X509(X509 *a, unsigned char **out); 62 | X509 *d2i_X509_bio(BIO *bp, X509 **x); 63 | 64 | // X509 store 65 | typedef struct x509_store_st X509_STORE; 66 | typedef struct X509_crl_st X509_CRL; 67 | X509_STORE *X509_STORE_new(void ); 68 | int X509_STORE_add_cert(X509_STORE *ctx, X509 *x); 69 | // Use this if we want to load the certs directly from a variables 70 | int X509_STORE_add_crl(X509_STORE *ctx, X509_CRL *x); 71 | int X509_STORE_load_locations (X509_STORE *ctx, 72 | const char *file, const char *dir); 73 | void X509_STORE_free(X509_STORE *v); 74 | 75 | // X509 store context 76 | typedef struct x509_store_ctx_st X509_STORE_CTX; 77 | X509_STORE_CTX *X509_STORE_CTX_new(void); 78 | int X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, 79 | X509 *x509, void *chain); 80 | int X509_verify_cert(X509_STORE_CTX *ctx); 81 | void X509_STORE_CTX_cleanup(X509_STORE_CTX *ctx); 82 | int X509_STORE_CTX_get_error(X509_STORE_CTX *ctx); 83 | const char *X509_verify_cert_error_string(long n); 84 | void X509_STORE_CTX_free(X509_STORE_CTX *ctx); 85 | 86 | // EVP Sign/Verify 87 | typedef struct env_md_ctx_st EVP_MD_CTX; 88 | typedef struct env_md_st EVP_MD; 89 | typedef struct evp_pkey_ctx_st EVP_PKEY_CTX; 90 | const EVP_MD *EVP_get_digestbyname(const char *name); 91 | EVP_MD_CTX *EVP_MD_CTX_create(void); 92 | void EVP_MD_CTX_destroy(EVP_MD_CTX *ctx); 93 | int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl); 94 | int EVP_DigestSignInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, 95 | const EVP_MD *type, ENGINE *e, EVP_PKEY *pkey); 96 | int EVP_DigestUpdate(EVP_MD_CTX *ctx,const void *d, 97 | size_t cnt); 98 | int EVP_DigestSignFinal(EVP_MD_CTX *ctx, 99 | unsigned char *sigret, size_t *siglen); 100 | 101 | int EVP_DigestVerifyInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, 102 | const EVP_MD *type, ENGINE *e, EVP_PKEY *pkey); 103 | int EVP_DigestVerifyFinal(EVP_MD_CTX *ctx, 104 | unsigned char *sig, size_t siglen); 105 | 106 | // Fingerprints 107 | int X509_digest(const X509 *data,const EVP_MD *type, 108 | unsigned char *md, unsigned int *len); 109 | 110 | ]] 111 | 112 | 113 | local function _err(ret) 114 | local code = _C.ERR_get_error() 115 | if code == 0 then 116 | return ret, "Zero error code (null arguments?)" 117 | end 118 | return ret, ffi.string(_C.ERR_reason_error_string(code)) 119 | end 120 | 121 | 122 | local RSASigner = {} 123 | _M.RSASigner = RSASigner 124 | 125 | --- Create a new RSASigner 126 | -- @param pem_private_key A private key string in PEM format 127 | -- @returns RSASigner, err_string 128 | function RSASigner.new(self, pem_private_key) 129 | local bio = _C.BIO_new(_C.BIO_s_mem()) 130 | ffi.gc(bio, _C.BIO_vfree) 131 | if _C.BIO_puts(bio, pem_private_key) < 0 then 132 | return _err() 133 | end 134 | 135 | -- TODO might want to support password protected private keys... 136 | local rsa = _C.PEM_read_bio_RSAPrivateKey(bio, nil, nil, nil) 137 | ffi.gc(rsa, _C.RSA_free) 138 | 139 | local evp_pkey = _C.EVP_PKEY_new() 140 | if not evp_pkey then 141 | return _err() 142 | end 143 | ffi.gc(evp_pkey, _C.EVP_PKEY_free) 144 | if _C.EVP_PKEY_set1_RSA(evp_pkey, rsa) ~= 1 then 145 | return _err() 146 | end 147 | self.evp_pkey = evp_pkey 148 | return self, nil 149 | end 150 | 151 | 152 | --- Sign a message 153 | -- @param message The message to sign 154 | -- @param digest_name The digest format to use (e.g., "SHA256") 155 | -- @returns signature, error_string 156 | function RSASigner.sign(self, message, digest_name) 157 | local buf = ffi.new("unsigned char[?]", 1024) 158 | local len = ffi.new("size_t[1]", 1024) 159 | 160 | local ctx = _C.EVP_MD_CTX_create() 161 | if not ctx then 162 | return _err() 163 | end 164 | ffi.gc(ctx, _C.EVP_MD_CTX_destroy) 165 | 166 | local md = _C.EVP_get_digestbyname(digest_name) 167 | if not md then 168 | return _err() 169 | end 170 | 171 | if _C.EVP_DigestInit_ex(ctx, md, nil) ~= 1 then 172 | return _err() 173 | end 174 | local ret = _C.EVP_DigestSignInit(ctx, nil, md, nil, self.evp_pkey) 175 | if ret ~= 1 then 176 | return _err() 177 | end 178 | if _C.EVP_DigestUpdate(ctx, message, #message) ~= 1 then 179 | return _err() 180 | end 181 | if _C.EVP_DigestSignFinal(ctx, buf, len) ~= 1 then 182 | return _err() 183 | end 184 | return ffi.string(buf, len[0]), nil 185 | end 186 | 187 | 188 | 189 | local RSAVerifier = {} 190 | _M.RSAVerifier = RSAVerifier 191 | 192 | 193 | --- Create a new RSAVerifier 194 | -- @param key_source An instance of Cert or PublicKey used for verification 195 | -- @returns RSAVerifier, error_string 196 | function RSAVerifier.new(self, key_source) 197 | if not key_source then 198 | return nil, "You must pass in an key_source for a public key" 199 | end 200 | local evp_public_key = key_source.public_key 201 | self.evp_pkey = evp_public_key 202 | return self, nil 203 | end 204 | 205 | --- Verify a message is properly signed 206 | -- @param message The original message 207 | -- @param the signature to verify 208 | -- @param digest_name The digest type that was used to sign 209 | -- @returns bool, error_string 210 | function RSAVerifier.verify(self, message, sig, digest_name) 211 | local md = _C.EVP_get_digestbyname(digest_name) 212 | if not md then 213 | return _err(false) 214 | end 215 | 216 | local ctx = _C.EVP_MD_CTX_create() 217 | if not ctx then 218 | return _err(false) 219 | end 220 | ffi.gc(ctx, _C.EVP_MD_CTX_destroy) 221 | 222 | if _C.EVP_DigestInit_ex(ctx, md, nil) ~= 1 then 223 | return _err(false) 224 | end 225 | 226 | local ret = _C.EVP_DigestVerifyInit(ctx, nil, md, nil, self.evp_pkey) 227 | if ret ~= 1 then 228 | return _err(false) 229 | end 230 | if _C.EVP_DigestUpdate(ctx, message, #message) ~= 1 then 231 | return _err(false) 232 | end 233 | local sig_bin = ffi.new("unsigned char[?]", #sig) 234 | ffi.copy(sig_bin, sig, #sig) 235 | if _C.EVP_DigestVerifyFinal(ctx, sig_bin, #sig) == 1 then 236 | return true, nil 237 | else 238 | return false, "Verification failed" 239 | end 240 | end 241 | 242 | 243 | local Cert = {} 244 | _M.Cert = Cert 245 | 246 | 247 | --- Create a new Certificate object 248 | -- @param payload A PEM or DER format X509 certificate 249 | -- @returns Cert, error_string 250 | function Cert.new(self, payload) 251 | if not payload then 252 | return nil, "Must pass a PEM or binary DER cert" 253 | end 254 | local bio = _C.BIO_new(_C.BIO_s_mem()) 255 | ffi.gc(bio, _C.BIO_vfree) 256 | local x509 257 | if payload:find('-----BEGIN') then 258 | if _C.BIO_puts(bio, payload) < 0 then 259 | return _err() 260 | end 261 | x509 = _C.PEM_read_bio_X509(bio, nil, nil, nil) 262 | else 263 | if _C.BIO_write(bio, payload, #payload) < 0 then 264 | return _err() 265 | end 266 | x509 = _C.d2i_X509_bio(bio, nil) 267 | end 268 | if not x509 then 269 | return _err() 270 | end 271 | ffi.gc(x509, _C.X509_free) 272 | self.x509 = x509 273 | local public_key, err = self:get_public_key() 274 | if not public_key then 275 | return nil, err 276 | end 277 | 278 | ffi.gc(public_key, _C.EVP_PKEY_free) 279 | 280 | self.public_key = public_key 281 | return self, nil 282 | end 283 | 284 | 285 | --- Retrieve the DER format of the certificate 286 | -- @returns Binary DER format 287 | function Cert.get_der(self) 288 | local bufp = ffi.new("unsigned char *[1]") 289 | local len = _C.i2d_X509(self.x509, bufp) 290 | if len < 0 then 291 | return _err() 292 | end 293 | local der = ffi.string(bufp[0], len) 294 | return der, nil 295 | end 296 | 297 | --- Retrieve the cert fingerprint 298 | -- @param digest_name the Type of digest to use (e.g., "SHA256") 299 | -- @returns fingerprint_string 300 | function Cert.get_fingerprint(self, digest_name) 301 | local md = _C.EVP_get_digestbyname(digest_name) 302 | if not md then 303 | return _err() 304 | end 305 | local buf = ffi.new("unsigned char[?]", 32) 306 | local len = ffi.new("unsigned int[1]", 32) 307 | if _C.X509_digest(self.x509, md, buf, len) ~= 1 then 308 | return _err() 309 | end 310 | local raw = ffi.string(buf, len[0]) 311 | local t = {} 312 | raw:gsub('.', function (c) table.insert(t, string.format('%02X', string.byte(c))) end) 313 | return table.concat(t, ":"), nil 314 | end 315 | 316 | --- Retrieve the public key from the CERT 317 | -- @returns An OpenSSL EVP PKEY object representing the public key 318 | function Cert.get_public_key(self) 319 | local evp_pkey = _C.X509_get_pubkey(self.x509) 320 | if not evp_pkey then 321 | return _err() 322 | end 323 | 324 | return evp_pkey, nil 325 | end 326 | 327 | --- Verify the Certificate is trusted 328 | -- @param trusted_cert_file File path to a list of PEM encoded trusted certificates 329 | -- @return bool, error_string 330 | function Cert.verify_trust(self, trusted_cert_file) 331 | local store = _C.X509_STORE_new() 332 | if not store then 333 | return _err(false) 334 | end 335 | ffi.gc(store, _C.X509_STORE_free) 336 | if _C.X509_STORE_load_locations(store, trusted_cert_file, nil) ~=1 then 337 | return _err(false) 338 | end 339 | 340 | local ctx = _C.X509_STORE_CTX_new() 341 | if not store then 342 | return _err(false) 343 | end 344 | ffi.gc(ctx, _C.X509_STORE_CTX_free) 345 | if _C.X509_STORE_CTX_init(ctx, store, self.x509, nil) ~= 1 then 346 | return _err(false) 347 | end 348 | 349 | if _C.X509_verify_cert(ctx) ~= 1 then 350 | local code = _C.X509_STORE_CTX_get_error(ctx) 351 | local msg = ffi.string(_C.X509_verify_cert_error_string(code)) 352 | _C.X509_STORE_CTX_cleanup(ctx) 353 | return false, msg 354 | end 355 | _C.X509_STORE_CTX_cleanup(ctx) 356 | return true, nil 357 | 358 | end 359 | 360 | local PublicKey = {} 361 | _M.PublicKey = PublicKey 362 | 363 | --- Create a new PublicKey object 364 | -- 365 | -- If a PEM fornatted key is provided, the key must start with 366 | -- 367 | -- ----- BEGIN PUBLIC KEY ----- 368 | -- 369 | -- @param payload A PEM or DER format public key file 370 | -- @return PublicKey, error_string 371 | function PublicKey.new(self, payload) 372 | if not payload then 373 | return nil, "Must pass a PEM or binary DER public key" 374 | end 375 | local bio = _C.BIO_new(_C.BIO_s_mem()) 376 | ffi.gc(bio, _C.BIO_vfree) 377 | local pkey 378 | if payload:find('-----BEGIN') then 379 | if _C.BIO_puts(bio, payload) < 0 then 380 | return _err() 381 | end 382 | pkey = _C.PEM_read_bio_PUBKEY(bio, nil, nil, nil) 383 | else 384 | if _C.BIO_write(bio, payload, #payload) < 0 then 385 | return _err() 386 | end 387 | pkey = _C.d2i_PUBKEY_bio(bio, nil) 388 | end 389 | if not pkey then 390 | return _err() 391 | end 392 | ffi.gc(pkey, _C.EVP_PKEY_free) 393 | self.public_key = pkey 394 | return self, nil 395 | end 396 | 397 | 398 | return _M 399 | -------------------------------------------------------------------------------- /lib/resty/jwt-validators.lua: -------------------------------------------------------------------------------- 1 | local _M = {_VERSION="0.1.5"} 2 | 3 | --[[ 4 | This file defines "validators" to be used in validating a spec. A "validator" is simply a function with 5 | a signature that matches: 6 | 7 | function(val, claim, jwt_json) 8 | 9 | This function returns either true or false. If a validator needs to give more information on why it failed, 10 | then it can also raise an error (which will be used in the "reason" part of the validated jwt_obj). If a 11 | validator returns nil, then it is assumed to have passed (same as returning true) and that you just forgot 12 | to actually return a value. 13 | 14 | There is a special claim name of "__jwt" that can be used to validate the entire jwt_obj. 15 | 16 | "val" is the value being tested. It may be nil if the claim doesn't exist in the jwt_obj. If the function 17 | is being called for the "__jwt" claim, then "val" will contain a deep clone of the full jwt object. 18 | 19 | "claim" is the claim that is being tested. It is passed in just in case a validator needs to do additional 20 | checks. It will be the string "__jwt" if the validator is being called for the entire jwt_object. 21 | 22 | "jwt_json" is a json-encoded representation of the full object that is being tested. It will never be nil, 23 | and can always be decoded using cjson.decode(jwt_json). 24 | ]]-- 25 | 26 | 27 | --[[ 28 | A function which will define a validator. It creates both "opt_" and required (non-"opt_") 29 | versions. The function that is passed in is the *optional* version. 30 | ]]-- 31 | local function define_validator(name, fx) 32 | _M["opt_" .. name] = fx 33 | _M[name] = function(...) return _M.chain(_M.required(), fx(...)) end 34 | end 35 | 36 | -- Validation messages 37 | local messages = { 38 | nil_validator = "Cannot create validator for nil %s.", 39 | wrong_type_validator = "Cannot create validator for non-%s %s.", 40 | empty_table_validator = "Cannot create validator for empty table %s.", 41 | wrong_table_type_validator = "Cannot create validator for non-%s table %s.", 42 | required_claim = "'%s' claim is required.", 43 | wrong_type_claim = "'%s' is malformed. Expected to be a %s.", 44 | missing_claim = "Missing one of claims - [ %s ]." 45 | } 46 | 47 | -- Local function to make sure that a value is non-nil or raises an error 48 | local function ensure_not_nil(v, e, ...) 49 | return v ~= nil and v or error(string.format(e, ...), 0) 50 | end 51 | 52 | -- Local function to make sure that a value is the given type 53 | local function ensure_is_type(v, t, e, ...) 54 | return type(v) == t and v or error(string.format(e, ...), 0) 55 | end 56 | 57 | -- Local function to make sure that a value is a (non-empty) table 58 | local function ensure_is_table(v, e, ...) 59 | ensure_is_type(v, "table", e, ...) 60 | return ensure_not_nil(next(v), e, ...) 61 | end 62 | 63 | -- Local function to make sure all entries in the table are the given type 64 | local function ensure_is_table_type(v, t, e, ...) 65 | if v ~= nil then 66 | ensure_is_table(v, e, ...) 67 | for _,val in ipairs(v) do 68 | ensure_is_type(val, t, e, ...) 69 | end 70 | end 71 | return v 72 | end 73 | 74 | -- Local function to ensure that a number is non-negative (positive or 0) 75 | local function ensure_is_non_negative(v, e, ...) 76 | if v ~= nil then 77 | ensure_is_type(v, "number", e, ...) 78 | if v >= 0 then 79 | return v 80 | else 81 | error(string.format(e, ...), 0) 82 | end 83 | end 84 | end 85 | 86 | -- A local function which returns simple equality 87 | local function equality_function(val, check) 88 | return val == check 89 | end 90 | 91 | -- A local function which returns string match 92 | local function string_match_function(val, pattern) 93 | return string.match(val, pattern) ~= nil 94 | end 95 | 96 | --[[ 97 | A local function which returns truth on existence of check in vals. 98 | Adopted from auth0/nginx-jwt table_contains by @twistedstream 99 | ]]-- 100 | local function table_contains_function(vals, check) 101 | for _, val in pairs(vals) do 102 | if val == check then return true end 103 | end 104 | return false 105 | end 106 | 107 | 108 | -- A local function which returns numeric greater than comparison 109 | local function greater_than_function(val, check) 110 | return val > check 111 | end 112 | 113 | -- A local function which returns numeric greater than or equal comparison 114 | local function greater_than_or_equal_function(val, check) 115 | return val >= check 116 | end 117 | 118 | -- A local function which returns numeric less than comparison 119 | local function less_than_function(val, check) 120 | return val < check 121 | end 122 | 123 | -- A local function which returns numeric less than or equal comparison 124 | local function less_than_or_equal_function(val, check) 125 | return val <= check 126 | end 127 | 128 | 129 | --[[ 130 | Returns a validator that chains the given functions together, one after 131 | another - as long as they keep passing their checks. 132 | ]]-- 133 | function _M.chain(...) 134 | local chain_functions = {...} 135 | for _, fx in ipairs(chain_functions) do 136 | ensure_is_type(fx, "function", messages.wrong_type_validator, "function", "chain_function") 137 | end 138 | 139 | return function(val, claim, jwt_json) 140 | for _, fx in ipairs(chain_functions) do 141 | if fx(val, claim, jwt_json) == false then 142 | return false 143 | end 144 | end 145 | return true 146 | end 147 | end 148 | 149 | --[[ 150 | Returns a validator that returns false if a value doesn't exist. If 151 | the value exists and a chain_function is specified, then the value of 152 | chain_function(val, claim, jwt_json) 153 | will be returned, otherwise, true will be returned. This allows for 154 | specifying that a value is both required *and* it must match some 155 | additional check. This function will be used in the "required_*" shortcut 156 | functions for simplification. 157 | ]]-- 158 | function _M.required(chain_function) 159 | if chain_function ~= nil then 160 | return _M.chain(_M.required(), chain_function) 161 | end 162 | 163 | return function(val, claim, jwt_json) 164 | ensure_not_nil(val, messages.required_claim, claim) 165 | return true 166 | end 167 | end 168 | 169 | --[[ 170 | Returns a validator which errors with a message if *NONE* of the given claim 171 | keys exist. It is expected that this function is used against a full jwt object. 172 | The claim_keys must be a non-empty table of strings. 173 | ]]-- 174 | function _M.require_one_of(claim_keys) 175 | ensure_not_nil(claim_keys, messages.nil_validator, "claim_keys") 176 | ensure_is_type(claim_keys, "table", messages.wrong_type_validator, "table", "claim_keys") 177 | ensure_is_table(claim_keys, messages.empty_table_validator, "claim_keys") 178 | ensure_is_table_type(claim_keys, "string", messages.wrong_table_type_validator, "string", "claim_keys") 179 | 180 | return function(val, claim, jwt_json) 181 | ensure_is_type(val, "table", messages.wrong_type_claim, claim, "table") 182 | ensure_is_type(val.payload, "table", messages.wrong_type_claim, claim .. ".payload", "table") 183 | 184 | for i, v in ipairs(claim_keys) do 185 | if val.payload[v] ~= nil then return true end 186 | end 187 | 188 | error(string.format(messages.missing_claim, table.concat(claim_keys, ", ")), 0) 189 | end 190 | end 191 | 192 | --[[ 193 | Returns a validator that checks if the result of calling the given function for 194 | the tested value and the check value returns true. The value of check_val and 195 | check_function cannot be nil. The optional name is used for error messages and 196 | defaults to "check_value". The optional check_type is used to make sure that 197 | the check type matches and defaults to type(check_val). The first parameter 198 | passed to check_function will *never* be nil (check succeeds if value is nil). 199 | Use the required version to fail on nil. If the check_function raises an 200 | error, that will be appended to the error message. 201 | ]]-- 202 | define_validator("check", function(check_val, check_function, name, check_type) 203 | name = name or "check_val" 204 | ensure_not_nil(check_val, messages.nil_validator, name) 205 | 206 | ensure_not_nil(check_function, messages.nil_validator, "check_function") 207 | ensure_is_type(check_function, "function", messages.wrong_type_validator, "function", "check_function") 208 | 209 | check_type = check_type or type(check_val) 210 | return function(val, claim, jwt_json) 211 | if val == nil then return true end 212 | 213 | ensure_is_type(val, check_type, messages.wrong_type_claim, claim, check_type) 214 | return check_function(val, check_val) 215 | end 216 | end) 217 | 218 | 219 | --[[ 220 | Returns a validator that checks if a value exactly equals the given check_value. 221 | If the value is nil, then this check succeeds. The value of check_val cannot be 222 | nil. 223 | ]]-- 224 | define_validator("equals", function(check_val) 225 | return _M.opt_check(check_val, equality_function, "check_val") 226 | end) 227 | 228 | 229 | --[[ 230 | Returns a validator that checks if a value matches the given pattern. The value 231 | of pattern must be a string. 232 | ]]-- 233 | define_validator("matches", function (pattern) 234 | ensure_is_type(pattern, "string", messages.wrong_type_validator, "string", "pattern") 235 | return _M.opt_check(pattern, string_match_function, "pattern", "string") 236 | end) 237 | 238 | 239 | --[[ 240 | Returns a validator which calls the given function for each of the given values 241 | and the tested value. If any of these calls return true, then this function 242 | returns true. The value of check_values must be a non-empty table with all the 243 | same types, and the value of check_function must not be nil. The optional name 244 | is used for error messages and defaults to "check_values". The optional 245 | check_type is used to make sure that the check type matches and defaults to 246 | type(check_values[1]) - the table type. 247 | ]]-- 248 | define_validator("any_of", function(check_values, check_function, name, check_type, table_type) 249 | name = name or "check_values" 250 | ensure_not_nil(check_values, messages.nil_validator, name) 251 | ensure_is_type(check_values, "table", messages.wrong_type_validator, "table", name) 252 | ensure_is_table(check_values, messages.empty_table_validator, name) 253 | 254 | table_type = table_type or type(check_values[1]) 255 | ensure_is_table_type(check_values, table_type, messages.wrong_table_type_validator, table_type, name) 256 | 257 | ensure_not_nil(check_function, messages.nil_validator, "check_function") 258 | ensure_is_type(check_function, "function", messages.wrong_type_validator, "function", "check_function") 259 | 260 | check_type = check_type or table_type 261 | return _M.opt_check(check_values, function(v1, v2) 262 | for i, v in ipairs(v2) do 263 | if check_function(v1, v) then return true end 264 | end 265 | return false 266 | end, name, check_type) 267 | end) 268 | 269 | 270 | --[[ 271 | Returns a validator that checks if a value exactly equals any of the given values. 272 | ]]-- 273 | define_validator("equals_any_of", function(check_values) 274 | return _M.opt_any_of(check_values, equality_function, "check_values") 275 | end) 276 | 277 | 278 | --[[ 279 | Returns a validator that checks if a value matches any of the given patterns. 280 | ]]-- 281 | define_validator("matches_any_of", function(patterns) 282 | return _M.opt_any_of(patterns, string_match_function, "patterns", "string", "string") 283 | end) 284 | 285 | --[[ 286 | Returns a validator that checks if a value of expected type string exists in any of the given values. 287 | The value of check_values must be a non-empty table with all the same types. 288 | The optional name is used for error messages and defaults to "check_values". 289 | ]]-- 290 | define_validator("contains_any_of", function(check_values, name) 291 | return _M.opt_any_of(check_values, table_contains_function, name, "table", "string") 292 | end) 293 | 294 | --[[ 295 | Returns a validator that checks how a value compares (numerically) to a given 296 | check_value. The value of check_val cannot be nil and must be a number. 297 | ]]-- 298 | define_validator("greater_than", function(check_val) 299 | ensure_is_type(check_val, "number", messages.wrong_type_validator, "number", "check_val") 300 | return _M.opt_check(check_val, greater_than_function, "check_val", "number") 301 | end) 302 | define_validator("greater_than_or_equal", function(check_val) 303 | ensure_is_type(check_val, "number", messages.wrong_type_validator, "number", "check_val") 304 | return _M.opt_check(check_val, greater_than_or_equal_function, "check_val", "number") 305 | end) 306 | define_validator("less_than", function(check_val) 307 | ensure_is_type(check_val, "number", messages.wrong_type_validator, "number", "check_val") 308 | return _M.opt_check(check_val, less_than_function, "check_val", "number") 309 | end) 310 | define_validator("less_than_or_equal", function(check_val) 311 | ensure_is_type(check_val, "number", messages.wrong_type_validator, "number", "check_val") 312 | return _M.opt_check(check_val, less_than_or_equal_function, "check_val", "number") 313 | end) 314 | 315 | 316 | --[[ 317 | A function to set the leeway (in seconds) used for is_not_before and is_not_expired. The 318 | default is to use 0 seconds 319 | ]]-- 320 | local system_leeway = 0 321 | function _M.set_system_leeway(leeway) 322 | ensure_is_type(leeway, "number", "leeway must be a non-negative number") 323 | ensure_is_non_negative(leeway, "leeway must be a non-negative number") 324 | system_leeway = leeway 325 | end 326 | 327 | 328 | --[[ 329 | A function to set the system clock used for is_not_before and is_not_expired. The 330 | default is to use ngx.now 331 | ]]-- 332 | local system_clock = ngx.now 333 | function _M.set_system_clock(clock) 334 | ensure_is_type(clock, "function", "clock must be a function") 335 | -- Check that clock returns the correct value 336 | local t = clock() 337 | ensure_is_type(t, "number", "clock function must return a non-negative number") 338 | ensure_is_non_negative(t, "clock function must return a non-negative number") 339 | system_clock = clock 340 | end 341 | 342 | -- Local helper function for date validation 343 | local function validate_is_date(val, claim, jwt_json) 344 | ensure_is_non_negative(val, messages.wrong_type_claim, claim, "positive numeric value") 345 | return true 346 | end 347 | 348 | -- Local helper for date formatting 349 | local function format_date_on_error(date_check_function, error_msg) 350 | ensure_is_type(date_check_function, "function", messages.wrong_type_validator, "function", "date_check_function") 351 | ensure_is_type(error_msg, "string", messages.wrong_type_validator, "string", error_msg) 352 | return function(val, claim, jwt_json) 353 | local ret = date_check_function(val, claim, jwt_json) 354 | if ret == false then 355 | error(string.format("'%s' claim %s %s", claim, error_msg, ngx.http_time(val)), 0) 356 | end 357 | return true 358 | end 359 | end 360 | 361 | --[[ 362 | Returns a validator that checks if the current time is not before the tested value 363 | within the system's leeway. This means that: 364 | val <= (system_clock() + system_leeway). 365 | ]]-- 366 | define_validator("is_not_before", function() 367 | return format_date_on_error( 368 | _M.chain(validate_is_date, 369 | function(val) 370 | return val and less_than_or_equal_function(val, (system_clock() + system_leeway)) 371 | end), 372 | "not valid until" 373 | ) 374 | end) 375 | 376 | 377 | --[[ 378 | Returns a validator that checks if the current time is not equal to or after the 379 | tested value within the system's leeway. This means that: 380 | val > (system_clock() - system_leeway). 381 | ]]-- 382 | define_validator("is_not_expired", function() 383 | return format_date_on_error( 384 | _M.chain(validate_is_date, 385 | function(val) 386 | return val and greater_than_function(val, (system_clock() - system_leeway)) 387 | end), 388 | "expired at" 389 | ) 390 | end) 391 | 392 | --[[ 393 | Returns a validator that checks if the current time is the same as the tested value 394 | within the system's leeway. This means that: 395 | val >= (system_clock() - system_leeway) and val <= (system_clock() + system_leeway). 396 | ]]-- 397 | define_validator("is_at", function() 398 | local now = system_clock() 399 | return format_date_on_error( 400 | _M.chain(validate_is_date, 401 | function(val) 402 | local now = system_clock() 403 | return val and 404 | greater_than_or_equal_function(val, now - system_leeway) and 405 | less_than_or_equal_function(val, now + system_leeway) 406 | end), 407 | "is only valid at" 408 | ) 409 | end) 410 | 411 | 412 | return _M 413 | -------------------------------------------------------------------------------- /lua-resty-jwt-dev-0.rockspec: -------------------------------------------------------------------------------- 1 | package = 'lua-resty-jwt' 2 | version = 'dev-0' 3 | source = { 4 | url = 'file://.' 5 | } 6 | description = { 7 | summary = 'JWT for ngx_lua and LuaJIT.', 8 | detailed = [[ 9 | This library requires an nginx build 10 | with OpenSSL, the ngx_lua module, 11 | the LuaJIT 2.0, the lua-resty-hmac, 12 | and the lua-resty-string, 13 | ]], 14 | homepage = 'https://github.com/SkyLothar/lua-resty-jwt', 15 | license = 'Apache License Version 2' 16 | } 17 | dependencies = { 18 | 'lua >= 5.1' 19 | } 20 | build = { 21 | type = 'builtin', 22 | modules = { 23 | ['resty.jwt'] = 'lib/resty/jwt.lua', 24 | ['resty.evp'] = 'lib/resty/evp.lua', 25 | ['resty.jwt-validators'] = 'lib/resty/jwt-validators.lua', 26 | ['resty.hmac'] = 'vendor/resty/hmac.lua' 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /t/function-secret.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(2); 4 | 5 | plan tests => repeat_each() * (3 * blocks()); 6 | 7 | our $HttpConfig = <<'_EOC_'; 8 | lua_package_path 'lib/?.lua;;'; 9 | _EOC_ 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | 18 | === TEST 1: JWT sign HS256 (with function secret) 19 | --- http_config eval: $::HttpConfig 20 | --- config 21 | location /t { 22 | content_by_lua ' 23 | local jwt = require "resty.jwt" 24 | local jwt_token = jwt:sign( 25 | function(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end, 26 | { 27 | header={typ="JWT",alg="HS256",kid="lua-resty-kid"}, 28 | raw_header=jwt:jwt_encode("{\\"typ\\":\\"JWT\\",\\"alg\\":\\"HS256\\",\\"kid\\":\\"lua-resty-kid\\"}"), 29 | raw_payload=jwt:jwt_encode("{\\"foo\\":\\"bar\\"}") 30 | } 31 | ) 32 | ngx.say(jwt_token) 33 | '; 34 | } 35 | --- request 36 | GET /t 37 | --- response_body 38 | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6Imx1YS1yZXN0eS1raWQifQ.eyJmb28iOiJiYXIifQ.oHF49PvVWPaLt2rx4K7vPlPq_hBES7YEmgOC_ObCd7w 39 | --- no_error_log 40 | [error] 41 | 42 | 43 | === TEST 2: JWT sign HS512 (with function secret) 44 | --- http_config eval: $::HttpConfig 45 | --- config 46 | location /t { 47 | content_by_lua ' 48 | local jwt = require "resty.jwt" 49 | local jwt_token = jwt:sign( 50 | function(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end, 51 | { 52 | header={typ="JWT",alg="HS512",kid="lua-resty-kid"}, 53 | raw_header=jwt:jwt_encode("{\\"typ\\":\\"JWT\\",\\"alg\\":\\"HS512\\",\\"kid\\":\\"lua-resty-kid\\"}"), 54 | raw_payload=jwt:jwt_encode("{\\"foo\\":\\"bar\\"}") 55 | } 56 | ) 57 | ngx.say(jwt_token) 58 | '; 59 | } 60 | --- request 61 | GET /t 62 | --- response_body 63 | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiIsImtpZCI6Imx1YS1yZXN0eS1raWQifQ.eyJmb28iOiJiYXIifQ.QrA-NGD-OyPH8xM4_NIAMHnCySCQT0kWKXfWS_a41Gmd_1-J2iNyXc05hvDVe-2OrwyTEQ2U_Lg7w18JhRSxdA 64 | --- no_error_log 65 | [error] 66 | 67 | 68 | === TEST 3: Function secret missing KID 69 | --- http_config eval: $::HttpConfig 70 | --- config 71 | location /t { 72 | content_by_lua ' 73 | local jwt = require "resty.jwt" 74 | local success, err = pcall(function() jwt:sign( 75 | function(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end, 76 | { 77 | header={typ="JWT",alg="HS256"}, 78 | raw_header=jwt:jwt_encode("{\\"typ\\":\\"JWT\\",\\"alg\\":\\"HS256\\"}"), 79 | raw_payload=jwt:jwt_encode("{\\"foo\\":\\"bar\\"}") 80 | } 81 | ) end) 82 | ngx.say(err["reason"]) 83 | '; 84 | } 85 | --- request 86 | GET /t 87 | --- response_body 88 | secret function specified without kid in header 89 | --- no_error_log 90 | [error] 91 | 92 | 93 | === TEST 4: Function secret wrong KID 94 | --- http_config eval: $::HttpConfig 95 | --- config 96 | location /t { 97 | content_by_lua ' 98 | local jwt = require "resty.jwt" 99 | local success, err = pcall(function() jwt:sign( 100 | function(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end, 101 | { 102 | header={typ="JWT",alg="HS256",kid="non-existant-kid"}, 103 | raw_header=jwt:jwt_encode("{\\"typ\\":\\"JWT\\",\\"alg\\":\\"HS256\\",\\"kid\\":\\"non-existant-kid\\"}"), 104 | raw_payload=jwt:jwt_encode("{\\"foo\\":\\"bar\\"}") 105 | } 106 | ) end) 107 | ngx.say(err["reason"]) 108 | '; 109 | } 110 | --- request 111 | GET /t 112 | --- response_body 113 | function returned nil for kid: non-existant-kid 114 | --- no_error_log 115 | [error] 116 | 117 | 118 | === TEST 5: Function secret verify invalid 119 | --- http_config eval: $::HttpConfig 120 | --- config 121 | location /t { 122 | content_by_lua ' 123 | local jwt = require "resty.jwt" 124 | local jwt_obj = jwt:verify( 125 | function(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end, 126 | "invalid-random-str" 127 | ) 128 | ngx.say(jwt_obj["verified"]) 129 | ngx.say(jwt_obj["reason"]) 130 | '; 131 | } 132 | --- request 133 | GET /t 134 | --- response_body 135 | false 136 | invalid jwt string 137 | --- no_error_log 138 | [error] 139 | 140 | === TEST 6: Function secret verify wrong signature 141 | --- http_config eval: $::HttpConfig 142 | --- config 143 | location /t { 144 | content_by_lua ' 145 | local jwt = require "resty.jwt" 146 | local jwt_obj = jwt:verify( 147 | function(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end, 148 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6Imx1YS1yZXN0eS1raWQifQ" .. 149 | ".eyJmb28iOiJiYXIifQ" .. 150 | ".signature" 151 | ) 152 | ngx.say(jwt_obj["verified"]) 153 | ngx.say(jwt_obj["reason"]) 154 | '; 155 | } 156 | --- request 157 | GET /t 158 | --- response_body 159 | false 160 | signature mismatch: signature 161 | --- no_error_log 162 | [error] 163 | 164 | === TEST 7: Function secret simple verify with no validation option 165 | --- http_config eval: $::HttpConfig 166 | --- config 167 | location /t { 168 | content_by_lua ' 169 | local jwt = require "resty.jwt" 170 | local jwt_obj = jwt:verify( 171 | function(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end, 172 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6Imx1YS1yZXN0eS1raWQifQ" .. 173 | ".eyJmb28iOiJiYXIifQ" .. 174 | ".oHF49PvVWPaLt2rx4K7vPlPq_hBES7YEmgOC_ObCd7w", 175 | { } 176 | ) 177 | ngx.say(jwt_obj["verified"]) 178 | ngx.say(jwt_obj["reason"]) 179 | '; 180 | } 181 | --- request 182 | GET /t 183 | --- response_body 184 | true 185 | everything is awesome~ :p 186 | --- no_error_log 187 | [error] 188 | 189 | 190 | === TEST 8: Function secret verify missing KID 191 | --- http_config eval: $::HttpConfig 192 | --- config 193 | location /t { 194 | content_by_lua ' 195 | local jwt = require "resty.jwt" 196 | local jwt_obj = jwt:verify( 197 | function(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end, 198 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 199 | ".eyJmb28iOiJiYXIifQ" .. 200 | ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY", 201 | { } 202 | ) 203 | ngx.say(jwt_obj["verified"]) 204 | ngx.say(jwt_obj["reason"]) 205 | '; 206 | } 207 | --- request 208 | GET /t 209 | --- response_body 210 | false 211 | secret function specified without kid in header 212 | --- no_error_log 213 | [error] 214 | 215 | 216 | === TEST 9: Function secret simple verify wrong KID 217 | --- http_config eval: $::HttpConfig 218 | --- config 219 | location /t { 220 | content_by_lua ' 221 | local jwt = require "resty.jwt" 222 | local jwt_obj = jwt:verify( 223 | function(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end, 224 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6Im5vbi1leGlzdGFudC1raWQifQ" .. 225 | ".eyJmb28iOiJiYXIifQ" .. 226 | ".8hZ5eQNcLDF0jxsE_3AmK4AdNM4eKG465_krN7pF-O8", 227 | { } 228 | ) 229 | ngx.say(jwt_obj["verified"]) 230 | ngx.say(jwt_obj["reason"]) 231 | '; 232 | } 233 | --- request 234 | GET /t 235 | --- response_body 236 | false 237 | function returned nil for kid: non-existant-kid 238 | --- no_error_log 239 | [error] 240 | 241 | 242 | === TEST 10: Function secret sign and verify 243 | --- http_config eval: $::HttpConfig 244 | --- config 245 | location /t { 246 | content_by_lua ' 247 | local jwt = require "resty.jwt" 248 | 249 | local function get_kid(kid) return kid == "lua-resty-kid" and "lua-resty-jwt" or nil end 250 | 251 | local jwt_token = jwt:sign( 252 | get_kid, 253 | { 254 | header={typ="JWT",alg="HS256",kid="lua-resty-kid"}, 255 | payload={foo="bar", exp=9999999999} 256 | } 257 | ) 258 | 259 | local jwt_obj = jwt:verify(get_kid, jwt_token) 260 | ngx.say(jwt_obj["verified"]) 261 | ngx.say(jwt_obj["reason"]) 262 | ngx.say(jwt_obj["payload"]["foo"]) 263 | '; 264 | } 265 | --- request 266 | GET /t 267 | --- response_body 268 | true 269 | everything is awesome~ :p 270 | bar 271 | --- no_error_log 272 | [error] 273 | 274 | -------------------------------------------------------------------------------- /t/load-verify-jwe.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(2); 4 | 5 | plan tests => repeat_each() * (3 * blocks()); 6 | 7 | our $HttpConfig = <<'_EOC_'; 8 | lua_package_path 'lib/?.lua;;'; 9 | _EOC_ 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | 18 | === TEST 1: Verify A256CBC-HS512 Direct Encryption with a Shared Symmetric Key 19 | --- http_config eval: $::HttpConfig 20 | --- config 21 | location /t { 22 | content_by_lua ' 23 | local jwt = require "resty.jwt" 24 | local cjson = require "cjson" 25 | local shared_key = "12341234123412341234123412341234" .. 26 | "12341234123412341234123412341234" 27 | 28 | local jwt_obj = jwt:verify( 29 | shared_key, 30 | "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0." .. 31 | ".M927Z_hNTmumFQE0rtRQCQ.nnd7AoE_2dgvws2-iay8qA.d" .. 32 | "kyZuuks4Qm9Cd7VfEVSs07pi_Kyt0INVHTTesUC2BM" 33 | ) 34 | ngx.say( 35 | cjson.encode(jwt_obj) 36 | ) 37 | '; 38 | } 39 | --- request 40 | GET /t 41 | --- response_body 42 | {"payload":{"foo":"bar"},"reason":"everything is awesome~ :p","header":{"alg":"dir","enc":"A256CBC-HS512"},"valid":true,"verified":true} 43 | --- no_error_log 44 | [error] 45 | 46 | === TEST 2: Verify A128CBC-HS256 Direct Encryption with a Shared Symmetric Key 47 | --- http_config eval: $::HttpConfig 48 | --- config 49 | location /t { 50 | content_by_lua ' 51 | local jwt = require "resty.jwt" 52 | local cjson = require "cjson" 53 | local shared_key = "12341234123412341234123412341234" 54 | 55 | local jwt_obj = jwt:verify( 56 | shared_key, 57 | "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." .. 58 | ".U6emIwy_yVkagUwQ4EjdFA.FrapgQVvG3uictQz9NPPMw.n" .. 59 | "MoW0ShdgCN0JHw472SJjQ" 60 | ) 61 | ngx.say( 62 | cjson.encode(jwt_obj) 63 | ) 64 | '; 65 | } 66 | --- request 67 | GET /t 68 | --- response_body 69 | {"payload":{"foo":"bar"},"reason":"everything is awesome~ :p","header":{"alg":"dir","enc":"A128CBC-HS256"},"valid":true,"verified":true} 70 | --- no_error_log 71 | [error] 72 | 73 | === TEST 3: Dont fail if extra chars added 74 | --- http_config eval: $::HttpConfig 75 | --- config 76 | location /t { 77 | content_by_lua ' 78 | local jwt = require "resty.jwt" 79 | local cjson = require "cjson" 80 | local shared_key = "12341234123412341234123412341234" 81 | 82 | local jwt_obj = jwt:verify( 83 | shared_key, 84 | "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." .. 85 | ".U6emIwy_yVkagUwQ4EjdFA.FrapgQVvG3uictQz9NPPMw.n" .. 86 | "MoW0ShdgCN0JHw472SJjQ" .. 87 | "xxx" 88 | 89 | ) 90 | ngx.say( 91 | "valid: ", jwt_obj.valid, "\\n", 92 | "verified: ", jwt_obj.verified 93 | ) 94 | '; 95 | } 96 | --- request 97 | GET /t 98 | --- response_body 99 | valid: true 100 | verified: false 101 | --- no_error_log 102 | [error] 103 | 104 | === TEST 4: Encode A128CBC-HS256 Direct Encryption 105 | --- http_config eval: $::HttpConfig 106 | --- config 107 | location /t { 108 | content_by_lua ' 109 | local jwt = require "resty.jwt" 110 | local cjson = require "cjson" 111 | local shared_key = "12341234123412341234123412341234" 112 | 113 | local table_of_jwt = { 114 | header = { alg = "dir", enc = "A128CBC-HS256" }, 115 | payload = { foo = "bar" }, 116 | } 117 | 118 | local jwt_token = jwt:sign(shared_key, table_of_jwt) 119 | local jwt_obj = jwt:verify(shared_key, jwt_token) 120 | 121 | ngx.say( 122 | cjson.encode(table_of_jwt.payload) == cjson.encode(jwt_obj.payload), "\\n", 123 | "valid: ", jwt_obj.valid, "\\n", 124 | "verified: ", jwt_obj.verified 125 | ) 126 | '; 127 | } 128 | --- request 129 | GET /t 130 | --- response_body 131 | true 132 | valid: true 133 | verified: true 134 | --- no_error_log 135 | [error] 136 | 137 | 138 | === TEST 5: Encode A256CBC-HS512 Direct Encryption 139 | --- http_config eval: $::HttpConfig 140 | --- config 141 | location /t { 142 | content_by_lua ' 143 | local jwt = require "resty.jwt" 144 | local cjson = require "cjson" 145 | local shared_key = "12341234123412341234123412341234" .. 146 | "12341234123412341234123412341234" 147 | 148 | local table_of_jwt = { 149 | header = { alg = "dir", enc = "A256CBC-HS512" }, 150 | payload = { foo = "bar" }, 151 | } 152 | 153 | local jwt_token = jwt:sign(shared_key, table_of_jwt) 154 | local jwt_obj = jwt:verify(shared_key, jwt_token) 155 | 156 | ngx.say( 157 | cjson.encode(table_of_jwt.payload) == cjson.encode(jwt_obj.payload), "\\n", 158 | "valid: ", jwt_obj.valid, "\\n", 159 | "verified: ", jwt_obj.verified 160 | ) 161 | '; 162 | } 163 | --- request 164 | GET /t 165 | --- response_body 166 | true 167 | valid: true 168 | verified: true 169 | --- no_error_log 170 | [error] 171 | -------------------------------------------------------------------------------- /t/load-verify.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(2); 4 | 5 | plan tests => repeat_each() * (3 * blocks()); 6 | 7 | our $HttpConfig = <<'_EOC_'; 8 | lua_package_path 'lib/?.lua;;'; 9 | _EOC_ 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | 18 | === TEST 1: JWT table encode 19 | --- http_config eval: $::HttpConfig 20 | --- config 21 | location /t { 22 | content_by_lua ' 23 | local jwt = require "resty.jwt" 24 | ngx.say( 25 | "urlsafe b64encoded {foo: bar}: ", 26 | jwt:jwt_encode({foo="bar"}) 27 | ) 28 | '; 29 | } 30 | --- request 31 | GET /t 32 | --- response_body 33 | urlsafe b64encoded {foo: bar}: eyJmb28iOiJiYXIifQ2 34 | --- no_error_log 35 | [error] 36 | 37 | 38 | === TEST 2: JWT str encode 39 | --- http_config eval: $::HttpConfig 40 | --- config 41 | location /t { 42 | content_by_lua ' 43 | local jwt = require "resty.jwt" 44 | ngx.say( 45 | "urlsafe b64encoded {foo: bar}: ", 46 | jwt:jwt_encode("{\\"foo\\":\\"bar\\"}") 47 | ) 48 | '; 49 | } 50 | --- request 51 | GET /t 52 | --- response_body 53 | urlsafe b64encoded {foo: bar}: eyJmb28iOiJiYXIifQ2 54 | --- no_error_log 55 | [error] 56 | 57 | 58 | === TEST 3: JWT table decode 59 | --- http_config eval: $::HttpConfig 60 | --- config 61 | location /t { 62 | content_by_lua ' 63 | local jwt = require "resty.jwt" 64 | local decoded = jwt:jwt_decode("eyJmb28iOiJiYXIifQ", true) 65 | ngx.say("table eyJmb28iOiJiYXIifQ2: foo=", decoded["foo"]) 66 | '; 67 | } 68 | --- request 69 | GET /t 70 | --- response_body 71 | table eyJmb28iOiJiYXIifQ2: foo=bar 72 | --- no_error_log 73 | [error] 74 | 75 | 76 | === TEST 4: JWT str decode 77 | --- http_config eval: $::HttpConfig 78 | --- config 79 | location /t { 80 | content_by_lua ' 81 | local jwt = require "resty.jwt" 82 | local decoded = jwt:jwt_decode("eyJmb28iOiJiYXIifQ") 83 | ngx.say("table eyJmb28iOiJiYXIifQ2: ", decoded) 84 | '; 85 | } 86 | --- request 87 | GET /t 88 | --- response_body 89 | table eyJmb28iOiJiYXIifQ2: {"foo":"bar"} 90 | --- no_error_log 91 | [error] 92 | 93 | 94 | === TEST 5: JWT load valid 95 | --- http_config eval: $::HttpConfig 96 | --- config 97 | location /t { 98 | content_by_lua ' 99 | local jwt = require "resty.jwt" 100 | local jwt_obj = jwt:load_jwt( 101 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 102 | ".eyJmb28iOiJiYXIifQ" .. 103 | ".signature" 104 | ) 105 | ngx.say("alg is: ", jwt_obj.header.alg ," foo is: ", jwt_obj.payload.foo) 106 | '; 107 | } 108 | --- request 109 | GET /t 110 | --- response_body 111 | alg is: HS256 foo is: bar 112 | --- no_error_log 113 | [error] 114 | 115 | 116 | === TEST 6: JWT load invalid part 117 | --- http_config eval: $::HttpConfig 118 | --- config 119 | location /t { 120 | content_by_lua ' 121 | local jwt = require "resty.jwt" 122 | local jwt_obj = jwt:load_jwt( 123 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 124 | ".eyJmb28iOiJiYXIifQbad-format" .. 125 | ".signature" 126 | ) 127 | ngx.say("reason: ", jwt_obj.reason) 128 | '; 129 | } 130 | --- request 131 | GET /t 132 | --- response_body 133 | reason: invalid payload: eyJmb28iOiJiYXIifQbad-format 134 | --- no_error_log 135 | [error] 136 | 137 | 138 | === TEST 7: JWT load invalid 139 | --- http_config eval: $::HttpConfig 140 | --- config 141 | location /t { 142 | content_by_lua ' 143 | local jwt = require "resty.jwt" 144 | local jwt_obj = jwt:load_jwt("invalid-random-str") 145 | ngx.say(jwt_obj["verified"]) 146 | ngx.say(jwt_obj["reason"]) 147 | '; 148 | } 149 | --- request 150 | GET /t 151 | --- response_body 152 | false 153 | invalid jwt string 154 | --- no_error_log 155 | [error] 156 | 157 | 158 | === TEST 8: JWT verify wrong signature 159 | --- http_config eval: $::HttpConfig 160 | --- config 161 | location /t { 162 | content_by_lua ' 163 | local jwt = require "resty.jwt" 164 | local jwt_str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 165 | ".eyJmb28iOiJiYXIifQ" .. 166 | ".signature" 167 | local jwt_obj = jwt:load_jwt(jwt_str) 168 | local verified_obj = jwt:verify_jwt_obj("lua-resty-jwt", jwt_obj) 169 | ngx.say(jwt_obj["verified"]) 170 | ngx.say(jwt_obj["reason"]) 171 | '; 172 | } 173 | --- request 174 | GET /t 175 | --- response_body 176 | false 177 | signature mismatch: signature 178 | --- no_error_log 179 | [error] 180 | 181 | 182 | === TEST 9: JWT simple verify with no validation option 183 | --- http_config eval: $::HttpConfig 184 | --- config 185 | location /t { 186 | content_by_lua ' 187 | local jwt = require "resty.jwt" 188 | local jwt_str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 189 | ".eyJmb28iOiJiYXIifQ" .. 190 | ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY" 191 | 192 | local jwt_obj = jwt:load_jwt(jwt_str) 193 | local verified_obj = jwt:verify_jwt_obj( 194 | "lua-resty-jwt", jwt_obj, { } 195 | ) 196 | ngx.say(jwt_obj["verified"]) 197 | ngx.say(jwt_obj["reason"]) 198 | '; 199 | } 200 | --- request 201 | GET /t 202 | --- response_body 203 | true 204 | everything is awesome~ :p 205 | --- no_error_log 206 | [error] 207 | 208 | 209 | === TEST 10: JWT simple with default lifetime grace period and valid exp 210 | --- http_config eval: $::HttpConfig 211 | --- config 212 | location /t { 213 | content_by_lua ' 214 | local jwt = require "resty.jwt" 215 | local jwt_str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 216 | ".eyJmb28iOiJiYXIiLCJleHAiOjk5OTk5OTk5OTl9" .. 217 | ".Y503HYultweqOpvvNF3fj2FTb_rH7ZwKAXap6cPqXjw" 218 | 219 | local jwt_obj = jwt:load_jwt(jwt_str) 220 | local verified_obj = jwt:verify_jwt_obj( 221 | "lua-resty-jwt", jwt_obj 222 | ) 223 | ngx.say(verified_obj["verified"]) 224 | ngx.say(verified_obj["reason"]) 225 | '; 226 | } 227 | --- request 228 | GET /t 229 | --- response_body 230 | true 231 | everything is awesome~ :p 232 | --- no_error_log 233 | [error] 234 | 235 | 236 | === TEST 11: JWT simple with a zero lifetime grace period and invalid exp 237 | --- http_config eval: $::HttpConfig 238 | --- config 239 | location /t { 240 | content_by_lua ' 241 | local jwt = require "resty.jwt" 242 | local jwt_str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 243 | ".eyJmb28iOiJiYXIiLCJleHAiOjB9" .. 244 | ".btivkb1guN1sQBYYVcrigEuNVvDOp1PDrbgaNSD3Whg" 245 | 246 | local jwt_obj = jwt:load_jwt(jwt_str) 247 | local verified_obj = jwt:verify_jwt_obj( 248 | "lua-resty-jwt", jwt_obj, 249 | { lifetime_grace_period = 0 } 250 | ) 251 | ngx.say(verified_obj["verified"]) 252 | ngx.say(verified_obj["reason"]) 253 | '; 254 | } 255 | --- request 256 | GET /t 257 | --- response_body 258 | false 259 | 'exp' claim expired at Thu, 01 Jan 1970 00:00:00 GMT 260 | --- no_error_log 261 | [error] 262 | 263 | 264 | === TEST 12: JWT simple with default lifetime grace period and valid nbf 265 | --- http_config eval: $::HttpConfig 266 | --- config 267 | location /t { 268 | content_by_lua ' 269 | local jwt = require "resty.jwt" 270 | local jwt_str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 271 | ".eyJmb28iOiJiYXIiLCJuYmYiOjB9" .. 272 | ".qZeWRQBHZhRcszwbiL7JV6Nf-irT75u4IHhoQBTqkzo" 273 | 274 | local jwt_obj = jwt:load_jwt(jwt_str) 275 | local verified_obj = jwt:verify_jwt_obj( 276 | "lua-resty-jwt", jwt_obj 277 | ) 278 | ngx.say(verified_obj["verified"]) 279 | ngx.say(verified_obj["reason"]) 280 | '; 281 | } 282 | --- request 283 | GET /t 284 | --- response_body 285 | true 286 | everything is awesome~ :p 287 | --- no_error_log 288 | [error] 289 | 290 | 291 | === TEST 13: JWT simple with a zero lifetime grace period and invalid nbf 292 | --- http_config eval: $::HttpConfig 293 | --- config 294 | location /t { 295 | content_by_lua ' 296 | local jwt = require "resty.jwt" 297 | local jwt_str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 298 | ".eyJmb28iOiJiYXIiLCJuYmYiOjk5OTk5OTk5OTl9" .. 299 | ".Wfu3owxbzlrb0GXvV0D22Si8WEDP0WeRGwZNPAoYHMI" 300 | 301 | local jwt_obj = jwt:load_jwt(jwt_str) 302 | local verified_obj = jwt:verify_jwt_obj( 303 | "lua-resty-jwt", jwt_obj, 304 | { lifetime_grace_period = 0 } 305 | ) 306 | ngx.say(verified_obj["verified"]) 307 | ngx.say(verified_obj["reason"]) 308 | '; 309 | } 310 | --- request 311 | GET /t 312 | --- response_body 313 | false 314 | 'nbf' claim not valid until Sat, 20 Nov 2286 17:46:39 GMT 315 | --- no_error_log 316 | [error] 317 | 318 | 319 | === TEST 14: JWT simple with super large lifetime grace period and invalid nbf 320 | --- http_config eval: $::HttpConfig 321 | --- config 322 | location /t { 323 | content_by_lua ' 324 | local jwt = require "resty.jwt" 325 | local jwt_str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 326 | ".eyJmb28iOiJiYXIiLCJuYmYiOjk5OTk5OTk5OTl9" .. 327 | ".Wfu3owxbzlrb0GXvV0D22Si8WEDP0WeRGwZNPAoYHMI" 328 | 329 | local jwt_obj = jwt:load_jwt(jwt_str) 330 | local verified_obj = jwt:verify_jwt_obj( 331 | "lua-resty-jwt", jwt_obj, 332 | { lifetime_grace_period = 9999999999 } 333 | ) 334 | ngx.say(verified_obj["verified"]) 335 | ngx.say(verified_obj["reason"]) 336 | '; 337 | } 338 | --- request 339 | GET /t 340 | --- response_body 341 | true 342 | everything is awesome~ :p 343 | --- no_error_log 344 | [error] 345 | 346 | 347 | === TEST 15: Verify valid RS256 signed jwt using a certificate 348 | --- http_config eval: $::HttpConfig 349 | --- config 350 | location /t { 351 | set $cert '-----BEGIN CERTIFICATE-----\nMIIC2jCCAkMCAg38MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG\nA1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE\nMRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl\nYiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw\nODIyMDUyNzQxWhcNMTcwODIxMDUyNzQxWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE\nCAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs\nZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0z9FeMynsC8+u\ndvX+LciZxnh5uRj4C9S6tNeeAlIGCfQYk0zUcNFCoCkTknNQd/YEiawDLNbxBqut\nbMDZ1aarys1a0lYmUeVLCIqvzBkPJTSQsCopQQ9V8WuT252zzNzs68dVGNdCJd5J\nNRQykpwexmnjPPv0mvj7i8XgG379TyW6P+WWV5okeUkXJ9eJS2ouDYdR2SM9BoVW\n+FgxDu6BmXhozW5EfsnajFp7HL8kQClI0QOc79yuKl3492rH6bzFsFn2lfwWy9ic\n7cP8EpCTeFp1tFaD+vxBhPZkeTQ1HKx6hQ5zeHIB5ySJJZ7af2W8r4eTGYzbdRW2\n4DDHCPhZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQMv+BFvGdMVzkQaQ3/+2noVz\n/uAKbzpEL8xTcxYyP3lkOeh4FoxiSWqy5pGFALdPONoDuYFpLhjJSZaEwuvjI/Tr\nrGhLV1pRG9frwDFshqD2Vaj4ENBCBh6UpeBop5+285zQ4SI7q4U9oSebUDJiuOx6\n+tZ9KynmrbJpTSi0+BM=\n-----END CERTIFICATE-----'; 352 | content_by_lua ' 353 | local jwt = require "resty.jwt" 354 | 355 | local function get_public_key(url, iss, kid) 356 | if iss ~= nil then 357 | error("Unexpected iss has been passed. Duh :(") 358 | end 359 | 360 | if kid ~= nil then 361 | error("Unexpected kid has been passed. Duh :(") 362 | end 363 | 364 | return ngx.var.cert 365 | end 366 | 367 | jwt:set_trusted_certs_file("/lua-resty-jwt/testcerts/root.pem") 368 | jwt:set_alg_whitelist({ RS256 = 1 }) 369 | jwt:set_x5u_content_retriever(get_public_key) 370 | 371 | local jwt_token = "eyJ4NXUiOiJodHRwczpcL1wvZHVtbXkuY29tXC9jZXJ0cyIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0" 372 | .. ".eyJmb28iOiJiYXIifQ" 373 | .. ".h4fOshUFSiVoSjV0zoJNXSaAFGIzFScI_VRHQYLefZ5uuGWWEd69q6GBx1XVN4er67WuKDTmgbsW5b_ya2eU89U6LC" 374 | .. "3r2Rdu9FtYmm4aoQ5WesvC7UI63gJrhLFcbQGv1eDDPANZh-k_aOhGQLBjxdx_J2n95eKlYfqH3aZHTPtSnF7lEV4ZR" 375 | .. "RsHbX3jgS2Kcx-DvNQ77A81yQsTWtECKE-fiUZ5nOMn172rOPWM-DYTimsyOzuRErqE0xoB1u8ClVxmb1Mrg4LWSPoz" 376 | .. "nv5vhd8JkOXMg_5zYii6p5eIegH58IpxNYuDQ-rSo320nOvZOU7d8UOeYixYeEcEc1fMlQ" 377 | 378 | local jwt_obj = jwt:verify(nil, jwt_token) 379 | ngx.say(jwt_obj["verified"]) 380 | ngx.say(jwt_obj["reason"]) 381 | ngx.say(jwt_obj["payload"]["foo"]) 382 | '; 383 | } 384 | --- request 385 | GET /t 386 | --- response_body 387 | true 388 | everything is awesome~ :p 389 | bar 390 | --- no_error_log 391 | [error] 392 | 393 | 394 | === TEST 16: Verify RS256 signed jwt with bogus signature using a certificate 395 | --- http_config eval: $::HttpConfig 396 | --- config 397 | location /t { 398 | set $cert '-----BEGIN CERTIFICATE-----\nMIIC2jCCAkMCAg38MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG\nA1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE\nMRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl\nYiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw\nODIyMDUyNzQxWhcNMTcwODIxMDUyNzQxWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE\nCAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs\nZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0z9FeMynsC8+u\ndvX+LciZxnh5uRj4C9S6tNeeAlIGCfQYk0zUcNFCoCkTknNQd/YEiawDLNbxBqut\nbMDZ1aarys1a0lYmUeVLCIqvzBkPJTSQsCopQQ9V8WuT252zzNzs68dVGNdCJd5J\nNRQykpwexmnjPPv0mvj7i8XgG379TyW6P+WWV5okeUkXJ9eJS2ouDYdR2SM9BoVW\n+FgxDu6BmXhozW5EfsnajFp7HL8kQClI0QOc79yuKl3492rH6bzFsFn2lfwWy9ic\n7cP8EpCTeFp1tFaD+vxBhPZkeTQ1HKx6hQ5zeHIB5ySJJZ7af2W8r4eTGYzbdRW2\n4DDHCPhZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQMv+BFvGdMVzkQaQ3/+2noVz\n/uAKbzpEL8xTcxYyP3lkOeh4FoxiSWqy5pGFALdPONoDuYFpLhjJSZaEwuvjI/Tr\nrGhLV1pRG9frwDFshqD2Vaj4ENBCBh6UpeBop5+285zQ4SI7q4U9oSebUDJiuOx6\n+tZ9KynmrbJpTSi0+BM=\n-----END CERTIFICATE-----'; 399 | content_by_lua ' 400 | local jwt = require "resty.jwt" 401 | 402 | local function get_public_key(url) 403 | return ngx.var.cert 404 | end 405 | 406 | jwt:set_trusted_certs_file("/lua-resty-jwt/testcerts/root.pem") 407 | jwt:set_alg_whitelist({ RS256 = 1 }) 408 | jwt:set_x5u_content_retriever(get_public_key) 409 | 410 | local jwt_token = "eyJ4NXUiOiJodHRwczpcL1wvZHVtbXkuY29tXC9jZXJ0cyIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0" 411 | .. ".eyJmb28iOiJiYXIifQ" 412 | .. ".h4fOshUFSiVoSjV0zoJNXSaAFGIzFScI_VRHQYLefZ5uuGWWEd69q6GBx1XVN4er67WuKDTmgbsW5b_ya2eU89U6LC" 413 | .. "3r2Rdu9FtYmm4aoQ5WesvC7UI63gJrhLFcbQGv1eDDPANZh-k_aOhGQLBjxdx_J2n95eKlYfqH3aZHTPtSnF7lEV4ZR" 414 | .. "RsHbX3jgS2Kcx-DvNQ77A81yQsTWtECKE-fiUZ5nOMn172rOPWM-DYTimsyOzuRErqE0xoB1u8ClVxmb1Mrg4LWSPoz" 415 | .. "nv5vhd8JkOXMg_5zYii6p5eIegH58IpxNYuDQ-rSo320nOvZOU7d8UOeYixYeEcEc1fMlQ" 416 | 417 | -- Alter the jwt 418 | jwt_token = jwt_token .. "123" 419 | 420 | local jwt_obj = jwt:verify(nil, jwt_token) 421 | ngx.say(jwt_obj["verified"]) 422 | ngx.say(jwt_obj["reason"]) 423 | '; 424 | } 425 | --- request 426 | GET /t 427 | --- response_body 428 | false 429 | Wrongly encoded signature 430 | --- no_error_log 431 | [error] 432 | 433 | === TEST 17: Verify valid RS256 signed jwt using a rsa public key 434 | --- http_config eval: $::HttpConfig 435 | --- config 436 | location /t { 437 | content_by_lua ' 438 | local jwt = require "resty.jwt" 439 | 440 | -- pubkey.pem 441 | local public_key = [[ 442 | -----BEGIN PUBLIC KEY----- 443 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtM/RXjMp7AvPrnb1/i3I 444 | mcZ4ebkY+AvUurTXngJSBgn0GJNM1HDRQqApE5JzUHf2BImsAyzW8QarrWzA2dWm 445 | q8rNWtJWJlHlSwiKr8wZDyU0kLAqKUEPVfFrk9uds8zc7OvHVRjXQiXeSTUUMpKc 446 | HsZp4zz79Jr4+4vF4Bt+/U8luj/llleaJHlJFyfXiUtqLg2HUdkjPQaFVvhYMQ7u 447 | gZl4aM1uRH7J2oxaexy/JEApSNEDnO/cripd+Pdqx+m8xbBZ9pX8FsvYnO3D/BKQ 448 | k3hadbRWg/r8QYT2ZHk0NRyseoUOc3hyAeckiSWe2n9lvK+HkxmM23UVtuAwxwj4 449 | WQIDAQAB 450 | -----END PUBLIC KEY----- 451 | ]] 452 | 453 | jwt:set_alg_whitelist({ RS256 = 1 }) 454 | local jwt_token = "eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiJ9." 455 | .. "eyJpc3MiOiAidGVzdCIsICJpYXQiOiAxNDYxOTE0MDE3fQ." 456 | .. "dng6Vc-p_ISwiWc61ifWahbFYKBNWfaIr-W3bTPpgL-awG8" 457 | .. "UlaCONkQk2PHJw_xndbpenQYl_-hipCKynokeFBTXVcSL6H" 458 | .. "7XL4D9laQVDVFnI63hcXOMQxgICsQPVdcfVSBl2jHyV8kuw" 459 | .. "XpUHbXQTxMawlE9SkI1-7UukxL9OyFIkT1D1uW7P96irVDs" 460 | .. "GkEdTLVUPJerH-jlW4rRbW9twSHsgzHgkaqnQ41giW_e2Zz" 461 | .. "r0U2euFH-AxlyvWBJd8Y7rQ_aD40USKsJilZ5qSykGZ7KHd" 462 | .. "PzuwTXioCwB8bGVE2YoL-DKYj7-tOwoNsMK7UJzyjqzHqwuqvZWtbhmeRlww" 463 | 464 | local jwt_obj = jwt:verify(public_key, jwt_token) 465 | ngx.say(jwt_obj["verified"]) 466 | ngx.say(jwt_obj["reason"]) 467 | ngx.say(jwt_obj["payload"]["iss"]) 468 | '; 469 | } 470 | --- request 471 | GET /t 472 | --- response_body 473 | true 474 | everything is awesome~ :p 475 | test 476 | --- no_error_log 477 | [error] 478 | 479 | === TEST 18: Verify RS256 signed jwt with bogus signature using a rsa public key 480 | --- http_config eval: $::HttpConfig 481 | --- config 482 | location /t { 483 | content_by_lua ' 484 | local jwt = require "resty.jwt" 485 | 486 | -- pubkey.pem 487 | local public_key = [[ 488 | -----BEGIN PUBLIC KEY----- 489 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtM/RXjMp7AvPrnb1/i3I 490 | mcZ4ebkY+AvUurTXngJSBgn0GJNM1HDRQqApE5JzUHf2BImsAyzW8QarrWzA2dWm 491 | q8rNWtJWJlHlSwiKr8wZDyU0kLAqKUEPVfFrk9uds8zc7OvHVRjXQiXeSTUUMpKc 492 | HsZp4zz79Jr4+4vF4Bt+/U8luj/llleaJHlJFyfXiUtqLg2HUdkjPQaFVvhYMQ7u 493 | gZl4aM1uRH7J2oxaexy/JEApSNEDnO/cripd+Pdqx+m8xbBZ9pX8FsvYnO3D/BKQ 494 | k3hadbRWg/r8QYT2ZHk0NRyseoUOc3hyAeckiSWe2n9lvK+HkxmM23UVtuAwxwj4 495 | WQIDAQAB 496 | -----END PUBLIC KEY----- 497 | ]] 498 | 499 | jwt:set_alg_whitelist({ RS256 = 1 }) 500 | local jwt_token = "eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiJ9." 501 | .. "eyJpc3MiOiAidGVzdCIsICJpYXQiOiAxNDYxOTE0MDE3fQ." 502 | .. "dng6Vc-p_ISwiWc61ifWahbFYKBNWfaIr-W3bTPpgL-awG8" 503 | .. "UlaCONkQk2PHJw_xndbpenQYl_-hipCKynokeFBTXVcSL6H" 504 | .. "7XL4D9laQVDVFnI63hcXOMQxgICsQPVdcfVSBl2jHyV8kuw" 505 | .. "XpUHbXQTxMawlE9SkI1-7UukxL9OyFIkT1D1uW7P96irVDs" 506 | .. "GkEdTLVUPJerH-jlW4rRbW9twSHsgzHgkaqnQ41giW_e2Zz" 507 | .. "r0U2euFH-AxlyvWBJd8Y7rQ_aD40USKsJilZ5qSykGZ7KHd" 508 | .. "PzuwTXioCwB8bGVE2YoL-DKYj7-tOwoNsMK7UJzyjqzHqwuqvZWtbhmeRlww" 509 | 510 | -- Alter the jwt 511 | jwt_token = jwt_token .. "123" 512 | 513 | local jwt_obj = jwt:verify(public_key, jwt_token) 514 | ngx.say(jwt_obj["verified"]) 515 | ngx.say(jwt_obj["reason"]) 516 | '; 517 | } 518 | --- request 519 | GET /t 520 | --- response_body 521 | false 522 | Wrongly encoded signature 523 | --- no_error_log 524 | [error] 525 | 526 | 527 | === TEST 19: make sure invalid RS256 is INVALID 528 | --- http_config eval: $::HttpConfig 529 | --- config 530 | location /t { 531 | set $cert '-----BEGIN CERTIFICATE-----\nMIIC2jCCAkMCAg38MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG\nA1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE\nMRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl\nYiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw\nODIyMDUyNzQxWhcNMTcwODIxMDUyNzQxWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE\nCAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs\nZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0z9FeMynsC8+u\ndvX+LciZxnh5uRj4C9S6tNeeAlIGCfQYk0zUcNFCoCkTknNQd/YEiawDLNbxBqut\nbMDZ1aarys1a0lYmUeVLCIqvzBkPJTSQsCopQQ9V8WuT252zzNzs68dVGNdCJd5J\nNRQykpwexmnjPPv0mvj7i8XgG379TyW6P+WWV5okeUkXJ9eJS2ouDYdR2SM9BoVW\n+FgxDu6BmXhozW5EfsnajFp7HL8kQClI0QOc79yuKl3492rH6bzFsFn2lfwWy9ic\n7cP8EpCTeFp1tFaD+vxBhPZkeTQ1HKx6hQ5zeHIB5ySJJZ7af2W8r4eTGYzbdRW2\n4DDHCPhZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQMv+BFvGdMVzkQaQ3/+2noVz\n/uAKbzpEL8xTcxYyP3lkOeh4FoxiSWqy5pGFALdPONoDuYFpLhjJSZaEwuvjI/Tr\nrGhLV1pRG9frwDFshqD2Vaj4ENBCBh6UpeBop5+285zQ4SI7q4U9oSebUDJiuOx6\n+tZ9KynmrbJpTSi0+BM=\n-----END CERTIFICATE-----'; 532 | content_by_lua ' 533 | local jwt = require "resty.jwt" 534 | 535 | local function get_public_key(url, iss, kid) 536 | if iss ~= nil then 537 | error("Unexpected iss has been passed. Duh :(") 538 | end 539 | 540 | if kid ~= nil then 541 | error("Unexpected kid has been passed. Duh :(") 542 | end 543 | 544 | return ngx.var.cert 545 | end 546 | 547 | jwt:set_trusted_certs_file("/lua-resty-jwt/testcerts/root.pem") 548 | jwt:set_alg_whitelist({ RS256 = 1 }) 549 | jwt:set_x5u_content_retriever(get_public_key) 550 | 551 | local jwt_token = "eyJ4NXUiOiJodHRwczpcL1wvZHVtbXkuY29tXC9jZXJ0cyIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0" 552 | .. ".eyJmb28iOiJiYXIifQ" 553 | .. ".h4fOshUFSiVoSjV0zoJNXSaAFGIzFScI_VRHQYLefZ5uuGWWEd69q6GBx1XVN4er67WuKDTmgbsW5b_ya2eU89U6LC" 554 | .. "3r2Rdu9FtYmm4aoQ5WesvC7UI63gJrhLFcbQGv1eDDPANZh-k_aOhGQLBjxdx_J2n95eKlYfqH3aZHTPtSnF7lEV4ZR" 555 | .. "RsHbX3jgS2Kcx-DvNQ77A81yQsTWtECKE-fiUZ5nOMn172rOPWM-DYTimsyOzuRErqE0xoB1u8ClVxmb1Mrg4LWSPoz" 556 | .. "nv5vhd8JkOXMg_5zYii6p5eIegH58IpxNYuDQ-rSo320nOvZOU7d8UOeYixYeEcEc1fMlQx" 557 | 558 | local jwt_obj = jwt:verify(nil, jwt_token) 559 | ngx.say(jwt_obj["verified"]) 560 | ngx.say(jwt_obj["reason"]) 561 | '; 562 | } 563 | --- request 564 | GET /t 565 | --- response_body 566 | false 567 | Verification failed 568 | --- no_error_log 569 | [error] 570 | -------------------------------------------------------------------------------- /t/sign-verify.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(2); 4 | 5 | plan tests => repeat_each() * (3 * blocks()); 6 | 7 | our $HttpConfig = <<'_EOC_'; 8 | lua_package_path 'lib/?.lua;;'; 9 | _EOC_ 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | 18 | === TEST 1: JWT sign HS256 19 | --- http_config eval: $::HttpConfig 20 | --- config 21 | location /t { 22 | content_by_lua ' 23 | local jwt = require "resty.jwt" 24 | local jwt_token = jwt:sign( 25 | "lua-resty-jwt", 26 | { 27 | header={typ="JWT",alg="HS256"}, 28 | raw_header=jwt:jwt_encode("{\\"typ\\":\\"JWT\\",\\"alg\\":\\"HS256\\"}"), 29 | raw_payload=jwt:jwt_encode("{\\"foo\\":\\"bar\\"}") 30 | } 31 | ) 32 | ngx.say(jwt_token) 33 | '; 34 | } 35 | --- request 36 | GET /t 37 | --- response_body 38 | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIifQ.VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY 39 | --- no_error_log 40 | [error] 41 | 42 | 43 | === TEST 2: JWT sign HS512 44 | --- http_config eval: $::HttpConfig 45 | --- config 46 | location /t { 47 | content_by_lua ' 48 | local jwt = require "resty.jwt" 49 | local jwt_token = jwt:sign( 50 | "lua-resty-jwt", 51 | { 52 | header={typ="JWT",alg="HS512"}, 53 | raw_header=jwt:jwt_encode("{\\"typ\\":\\"JWT\\",\\"alg\\":\\"HS512\\"}"), 54 | raw_payload=jwt:jwt_encode("{\\"foo\\":\\"bar\\"}") 55 | } 56 | ) 57 | ngx.say(jwt_token) 58 | '; 59 | } 60 | --- request 61 | GET /t 62 | --- response_body 63 | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJmb28iOiJiYXIifQ._r7cUx1935GlmpI41mElmYQJlY4LqAZ50mdLyPUaVfbbC13Afhi6NmrqQvk1yefSSIn3ZOJ0h9Rvwm_RtbsInA 64 | --- no_error_log 65 | [error] 66 | 67 | 68 | === TEST 3: JWT verify invalid 69 | --- http_config eval: $::HttpConfig 70 | --- config 71 | location /t { 72 | content_by_lua ' 73 | local jwt = require "resty.jwt" 74 | local jwt_obj = jwt:verify( 75 | "lua-resty-jwt", "invalid-random-str" 76 | ) 77 | ngx.say(jwt_obj["verified"]) 78 | ngx.say(jwt_obj["reason"]) 79 | '; 80 | } 81 | --- request 82 | GET /t 83 | --- response_body 84 | false 85 | invalid jwt string 86 | --- no_error_log 87 | [error] 88 | 89 | 90 | === TEST 4: JWT verify wrong signature 91 | --- http_config eval: $::HttpConfig 92 | --- config 93 | location /t { 94 | content_by_lua ' 95 | local jwt = require "resty.jwt" 96 | local jwt_obj = jwt:verify( 97 | "lua-resty-jwt", 98 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 99 | ".eyJmb28iOiJiYXIifQ" .. 100 | ".signature" 101 | ) 102 | ngx.say(jwt_obj["verified"]) 103 | ngx.say(jwt_obj["reason"]) 104 | '; 105 | } 106 | --- request 107 | GET /t 108 | --- response_body 109 | false 110 | signature mismatch: signature 111 | --- no_error_log 112 | [error] 113 | 114 | 115 | === TEST 5: JWT simple verify with no validation option 116 | --- http_config eval: $::HttpConfig 117 | --- config 118 | location /t { 119 | content_by_lua ' 120 | local jwt = require "resty.jwt" 121 | local jwt_obj = jwt:verify( 122 | "lua-resty-jwt", 123 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 124 | ".eyJmb28iOiJiYXIifQ" .. 125 | ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY", 126 | { } 127 | ) 128 | ngx.say(jwt_obj["verified"]) 129 | ngx.say(jwt_obj["reason"]) 130 | '; 131 | } 132 | --- request 133 | GET /t 134 | --- response_body 135 | true 136 | everything is awesome~ :p 137 | --- no_error_log 138 | [error] 139 | 140 | 141 | === TEST 6: JWT sign and verify 142 | --- http_config eval: $::HttpConfig 143 | --- config 144 | location /t { 145 | content_by_lua ' 146 | local jwt = require "resty.jwt" 147 | 148 | local jwt_token = jwt:sign( 149 | "lua-resty-jwt", 150 | { 151 | header={typ="JWT",alg="HS256"}, 152 | payload={foo="bar", exp=9999999999} 153 | } 154 | ) 155 | 156 | local jwt_obj = jwt:verify("lua-resty-jwt", jwt_token) 157 | ngx.say(jwt_obj["verified"]) 158 | ngx.say(jwt_obj["reason"]) 159 | ngx.say(jwt_obj["payload"]["foo"]) 160 | '; 161 | } 162 | --- request 163 | GET /t 164 | --- response_body 165 | true 166 | everything is awesome~ :p 167 | bar 168 | --- no_error_log 169 | [error] 170 | 171 | 172 | === TEST 7: JWT sign and verify RS256 173 | --- http_config eval: $::HttpConfig 174 | --- config 175 | location /t { 176 | set $cert '-----BEGIN CERTIFICATE-----\nMIIEEDCCAvigAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhDETMBEGCgmSJomT8ixk\nARkWA2NvbTEWMBQGCgmSJomT8ixkARkWBmRvY2tlcjETMBEGA1UECgwKRG9ja2Vy\nIEluYzEfMB0GA1UECwwWRG9ja2VyIFRlc3QgU2lnbmluZyBDQTEfMB0GA1UEAwwW\nRG9ja2VyIFRlc3QgU2lnbmluZyBDQTAeFw0xNTA1MTMyMzAxMzlaFw0xNzA1MTIy\nMzAxMzlaMGsxEzARBgoJkiaJk/IsZAEZFgNjb20xFjAUBgoJkiaJk/IsZAEZFgZk\nb2NrZXIxEzARBgNVBAoMCkRvY2tlciBJbmMxDDAKBgNVBAsMA2h1YjEZMBcGA1UE\nAwwQaHViZ3cuZG9ja2VyLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBANt4HzdXTDX9yklf5wemrt39peRmdJAHpodYn3PQBhkv8VGEU5+kpV8M9zg5\nj7SSjOED9+3LZ09l6iTBT+dQCJMEgyHN1gHPzkyjklrKfCC6yIKUIt+I/oUoHm3e\nloACrJZXYniZxCpiDf5JeitHCaVxJRGj+MXORe5SVY+V7MLlA3NSDHX6gIhknq59\nBOmSVJyww3Ka54kYtZwz3KZzMdrfvCdkjiCeD1zMJwYX6IU9z746DfHNuMnsH7Di\nmrzhoX/3CMFj9sXdpcrzFSlMDqUxIQunFyN6G4JKZywf9Dvs30Ay6q8gBhursRFd\nx6otDl0z7tqfJkA3e4GSt4/DR7MCAwEAAaOBpDCBoTAOBgNVHQ8BAf8EBAMCBLAw\nCQYDVR0TBAIwADAnBgNVHSUEIDAeBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUF\nBwMDMB0GA1UdDgQWBBQa440jFVFkVPsnGp3sp2bEdtz+6jAfBgNVHSMEGDAWgBQi\nzqX0zdgLeF6GAy7a+zvT4pBCLjAbBgNVHREEFDASghBodWJndy5kb2NrZXIuY29t\nMA0GCSqGSIb3DQEBBQUAA4IBAQB8PdN14gs6zB2m2eB1JpyoFSwho4O1z9UkZqdb\nGUB5SmElVydR2nOMLLUuJoa1zTd1FcX0zlHtDKuRXmO7xaPtrH4Uxiq3Lpu7i2WB\nUDTnMXuzX7hzc5DeU7k7mMbwKRkb+kijJTkET6wcaJwwDSyhzOCZgXW3V0WjHdbD\nprJc52tUk3CHOZY8iZWDVf9gHQpVeJnKzospXJRs3qQksEzD6ciRXu0VHnxeC0DP\n5pnQC17Kkm1QOCHRrsIGDHk9wB1zJlMoeMgIPrCLcC0GappmUlHSMka+3MZYgeW4\nAVlzdia3EYSUuM8Gb5tjSSm/6rTOnXR+zL4PBhNZdAD17hyY\n-----END CERTIFICATE-----'; 177 | set $key '-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbeB83V0w1/cpJ\nX+cHpq7d/aXkZnSQB6aHWJ9z0AYZL/FRhFOfpKVfDPc4OY+0kozhA/fty2dPZeok\nwU/nUAiTBIMhzdYBz85Mo5JaynwgusiClCLfiP6FKB5t3paAAqyWV2J4mcQqYg3+\nSXorRwmlcSURo/jFzkXuUlWPlezC5QNzUgx1+oCIZJ6ufQTpklScsMNymueJGLWc\nM9ymczHa37wnZI4gng9czCcGF+iFPc++Og3xzbjJ7B+w4pq84aF/9wjBY/bF3aXK\n8xUpTA6lMSELpxcjehuCSmcsH/Q77N9AMuqvIAYbq7ERXceqLQ5dM+7anyZAN3uB\nkrePw0ezAgMBAAECggEBAIZga0SYN/qK9ROuG6fsn/8eMjfBn7ccaBNQ6PihM0qy\ntyABVK5XwkWLi8cqP1oBrS6NHn3D3/KWZSGyFzl7IHTb+2p0PIeJdDgqow7iEdR8\naQ7CowOZPrXLFa6R7jZc7M10nb9X7utAdG7xEFN1QGvC9j5x1n1OyjScxvSOiJPf\nQMyR7pAUW3GoNYBdgs8YWmMU7SiS8hp4LRsQ2aJpZUexUVk3o1E3ZIeM/joE+jFZ\nNX+rL0Aqmn0JVgKqZI2CbDB5AwJdSpUYd/m0Ko3E/Hxdu/CbeJqdjSl4ArVIyRBL\nJWY4aMbC81B6ftYGyjpBujXIoyTYNIhf+CYYXzNrsOkCgYEA8J4ajDpKtxowkbIj\nT+9FTY54d+SWcvwEKA22KayyFwx/mXWTnfsylzF6JbGAiZKSG9gbUOP1LsoGDsGj\nhFdLMSfmi151IbOJg1l74E7nM5bqW9ULGyLBOpWlvopXt31jV6uQd/t/+gdflyw0\nMtr642krwfz8UOshkeEIsk+7mmUCgYEA6X/omGsvMyLpa2IWjxwuw+51pCdIoZvL\n7BpV1YHqwO8jWVv1KKpOKzrYZVu93w0WMCHoG/g9FGQtOBmMRk2OKOxmCF+H1or4\nI1+iI/dhTPA2tEeAELX+yGtu4fpEHe+dga1QCNdq06366rn0bhWB5hst1DdVQnXn\n+0KjhLmg7DcCgYAxqQ/lnSpKfBdGGrP7DXEKPrtSU1VRyf25norYMxJWe3fiXkfn\nNS8N0WJaYTYcLqoFIScSHNo/m+aAKSrsZ2/XZ1rHrOkT2ZAqEc/lTaOeHCmmZmPy\nZ8vloXkhyD+uWSylrX0VpkyVd+wcsTzcuiFJyi0Dzojs0nqNNxqqYpZfmQKBgDep\npUIIcyUGkoxlwqj09/T/OI4cS0UzRaaQFJwkL1k06MFZmZTLHH1TtthayWWN0hdB\nTfq076KXyuvPs0/jFxuMVzpxw4kScdrE5nsactiLfw706IOTTxxp9/Ho3iogv/R0\n41poN/AkTmd8UteXSvMW0ZMAadPBFb8hAKgYNFN7AoGAbw6WYYPnhikP7gqWmTh0\n0SOeET5WbRfseiUWMfLGL+R5GmNiBhw63n8srz+KJVLJv0PJ/WKDR7o/Hxjv3w9c\ndkqcFG1MW2NKjrdxztptrRaSD7JyA750rB+CsrwvqzJCvwbulp1iRfWkTqq2fnUU\nQvpxhOYnq/ZlXPjUiE5w67w=\n-----END PRIVATE KEY-----'; 178 | set $pub_key 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA23gfN1dMNf3KSV/nB6au3f2l5GZ0kAemh1ifc9AGGS/xUYRTn6SlXwz3ODmPtJKM4QP37ctnT2XqJMFP51AIkwSDIc3WAc/OTKOSWsp8ILrIgpQi34j+hSgebd6WgAKslldieJnEKmIN/kl6K0cJpXElEaP4xc5F7lJVj5XswuUDc1IMdfqAiGSern0E6ZJUnLDDcprniRi1nDPcpnMx2t+8J2SOIJ4PXMwnBhfohT3PvjoN8c24yewfsOKavOGhf/cIwWP2xd2lyvMVKUwOpTEhC6cXI3obgkpnLB/0O+zfQDLqryAGG6uxEV3Hqi0OXTPu2p8mQDd7gZK3j8NHswIDAQAB'; 179 | content_by_lua ' 180 | local jwt = require "resty.jwt" 181 | 182 | local jwt_token = jwt:sign( 183 | ngx.var.key, 184 | { 185 | header={ 186 | typ="JWT", 187 | alg="RS256", 188 | x5c={ 189 | ngx.var.pub_key, 190 | } }, 191 | payload={foo="bar", exp=9999999999} 192 | } 193 | ) 194 | 195 | local jwt_obj = jwt:verify(ngx.var.cert, jwt_token) 196 | ngx.say(jwt_obj["verified"]) 197 | ngx.say(jwt_obj["reason"]) 198 | ngx.say(jwt_obj["payload"]["foo"]) 199 | '; 200 | } 201 | --- request 202 | GET /t 203 | --- response_body 204 | true 205 | everything is awesome~ :p 206 | bar 207 | --- no_error_log 208 | [error] 209 | 210 | 211 | === TEST 8: RS256 malformed header 212 | --- http_config eval: $::HttpConfig 213 | --- config 214 | location /t { 215 | set $cert '-----BEGIN CERTIFICATE-----\nMIIEEDCCAvigAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhDETMBEGCgmSJomT8ixk\nARkWA2NvbTEWMBQGCgmSJomT8ixkARkWBmRvY2tlcjETMBEGA1UECgwKRG9ja2Vy\nIEluYzEfMB0GA1UECwwWRG9ja2VyIFRlc3QgU2lnbmluZyBDQTEfMB0GA1UEAwwW\nRG9ja2VyIFRlc3QgU2lnbmluZyBDQTAeFw0xNTA1MTMyMzAxMzlaFw0xNzA1MTIy\nMzAxMzlaMGsxEzARBgoJkiaJk/IsZAEZFgNjb20xFjAUBgoJkiaJk/IsZAEZFgZk\nb2NrZXIxEzARBgNVBAoMCkRvY2tlciBJbmMxDDAKBgNVBAsMA2h1YjEZMBcGA1UE\nAwwQaHViZ3cuZG9ja2VyLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBANt4HzdXTDX9yklf5wemrt39peRmdJAHpodYn3PQBhkv8VGEU5+kpV8M9zg5\nj7SSjOED9+3LZ09l6iTBT+dQCJMEgyHN1gHPzkyjklrKfCC6yIKUIt+I/oUoHm3e\nloACrJZXYniZxCpiDf5JeitHCaVxJRGj+MXORe5SVY+V7MLlA3NSDHX6gIhknq59\nBOmSVJyww3Ka54kYtZwz3KZzMdrfvCdkjiCeD1zMJwYX6IU9z746DfHNuMnsH7Di\nmrzhoX/3CMFj9sXdpcrzFSlMDqUxIQunFyN6G4JKZywf9Dvs30Ay6q8gBhursRFd\nx6otDl0z7tqfJkA3e4GSt4/DR7MCAwEAAaOBpDCBoTAOBgNVHQ8BAf8EBAMCBLAw\nCQYDVR0TBAIwADAnBgNVHSUEIDAeBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUF\nBwMDMB0GA1UdDgQWBBQa440jFVFkVPsnGp3sp2bEdtz+6jAfBgNVHSMEGDAWgBQi\nzqX0zdgLeF6GAy7a+zvT4pBCLjAbBgNVHREEFDASghBodWJndy5kb2NrZXIuY29t\nMA0GCSqGSIb3DQEBBQUAA4IBAQB8PdN14gs6zB2m2eB1JpyoFSwho4O1z9UkZqdb\nGUB5SmElVydR2nOMLLUuJoa1zTd1FcX0zlHtDKuRXmO7xaPtrH4Uxiq3Lpu7i2WB\nUDTnMXuzX7hzc5DeU7k7mMbwKRkb+kijJTkET6wcaJwwDSyhzOCZgXW3V0WjHdbD\nprJc52tUk3CHOZY8iZWDVf9gHQpVeJnKzospXJRs3qQksEzD6ciRXu0VHnxeC0DP\n5pnQC17Kkm1QOCHRrsIGDHk9wB1zJlMoeMgIPrCLcC0GappmUlHSMka+3MZYgeW4\nAVlzdia3EYSUuM8Gb5tjSSm/6rTOnXR+zL4PBhNZdAD17hyY\n-----END CERTIFICATE-----'; 216 | set $key '-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbeB83V0w1/cpJ\nX+cHpq7d/aXkZnSQB6aHWJ9z0AYZL/FRhFOfpKVfDPc4OY+0kozhA/fty2dPZeok\nwU/nUAiTBIMhzdYBz85Mo5JaynwgusiClCLfiP6FKB5t3paAAqyWV2J4mcQqYg3+\nSXorRwmlcSURo/jFzkXuUlWPlezC5QNzUgx1+oCIZJ6ufQTpklScsMNymueJGLWc\nM9ymczHa37wnZI4gng9czCcGF+iFPc++Og3xzbjJ7B+w4pq84aF/9wjBY/bF3aXK\n8xUpTA6lMSELpxcjehuCSmcsH/Q77N9AMuqvIAYbq7ERXceqLQ5dM+7anyZAN3uB\nkrePw0ezAgMBAAECggEBAIZga0SYN/qK9ROuG6fsn/8eMjfBn7ccaBNQ6PihM0qy\ntyABVK5XwkWLi8cqP1oBrS6NHn3D3/KWZSGyFzl7IHTb+2p0PIeJdDgqow7iEdR8\naQ7CowOZPrXLFa6R7jZc7M10nb9X7utAdG7xEFN1QGvC9j5x1n1OyjScxvSOiJPf\nQMyR7pAUW3GoNYBdgs8YWmMU7SiS8hp4LRsQ2aJpZUexUVk3o1E3ZIeM/joE+jFZ\nNX+rL0Aqmn0JVgKqZI2CbDB5AwJdSpUYd/m0Ko3E/Hxdu/CbeJqdjSl4ArVIyRBL\nJWY4aMbC81B6ftYGyjpBujXIoyTYNIhf+CYYXzNrsOkCgYEA8J4ajDpKtxowkbIj\nT+9FTY54d+SWcvwEKA22KayyFwx/mXWTnfsylzF6JbGAiZKSG9gbUOP1LsoGDsGj\nhFdLMSfmi151IbOJg1l74E7nM5bqW9ULGyLBOpWlvopXt31jV6uQd/t/+gdflyw0\nMtr642krwfz8UOshkeEIsk+7mmUCgYEA6X/omGsvMyLpa2IWjxwuw+51pCdIoZvL\n7BpV1YHqwO8jWVv1KKpOKzrYZVu93w0WMCHoG/g9FGQtOBmMRk2OKOxmCF+H1or4\nI1+iI/dhTPA2tEeAELX+yGtu4fpEHe+dga1QCNdq06366rn0bhWB5hst1DdVQnXn\n+0KjhLmg7DcCgYAxqQ/lnSpKfBdGGrP7DXEKPrtSU1VRyf25norYMxJWe3fiXkfn\nNS8N0WJaYTYcLqoFIScSHNo/m+aAKSrsZ2/XZ1rHrOkT2ZAqEc/lTaOeHCmmZmPy\nZ8vloXkhyD+uWSylrX0VpkyVd+wcsTzcuiFJyi0Dzojs0nqNNxqqYpZfmQKBgDep\npUIIcyUGkoxlwqj09/T/OI4cS0UzRaaQFJwkL1k06MFZmZTLHH1TtthayWWN0hdB\nTfq076KXyuvPs0/jFxuMVzpxw4kScdrE5nsactiLfw706IOTTxxp9/Ho3iogv/R0\n41poN/AkTmd8UteXSvMW0ZMAadPBFb8hAKgYNFN7AoGAbw6WYYPnhikP7gqWmTh0\n0SOeET5WbRfseiUWMfLGL+R5GmNiBhw63n8srz+KJVLJv0PJ/WKDR7o/Hxjv3w9c\ndkqcFG1MW2NKjrdxztptrRaSD7JyA750rB+CsrwvqzJCvwbulp1iRfWkTqq2fnUU\nQvpxhOYnq/ZlXPjUiE5w67w=\n-----END PRIVATE KEY-----'; 217 | content_by_lua ' 218 | local jwt = require "resty.jwt" 219 | jwt:set_trusted_certs_file("/path/to/cert") 220 | 221 | local jwt_token = jwt:sign( 222 | ngx.var.key, 223 | { 224 | header={ 225 | typ="JWT", 226 | alg="RS256", 227 | x5c={nil, } }, 228 | payload={foo="bar"} 229 | } 230 | ) 231 | 232 | local jwt_obj = jwt:verify(ngx.var.cert, jwt_token) 233 | ngx.say(jwt_obj["verified"]) 234 | ngx.say(jwt_obj["reason"]) 235 | ngx.say(jwt_obj["payload"]["foo"]) 236 | '; 237 | } 238 | --- request 239 | GET /t 240 | --- response_body 241 | false 242 | Unsupported RS256 key model 243 | bar 244 | --- no_error_log 245 | [error] 246 | 247 | 248 | === TEST 9: RS256 malformed header 2 249 | --- http_config eval: $::HttpConfig 250 | --- config 251 | location /t { 252 | set $cert '-----BEGIN CERTIFICATE-----\nMIIEEDCCAvigAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhDETMBEGCgmSJomT8ixk\nARkWA2NvbTEWMBQGCgmSJomT8ixkARkWBmRvY2tlcjETMBEGA1UECgwKRG9ja2Vy\nIEluYzEfMB0GA1UECwwWRG9ja2VyIFRlc3QgU2lnbmluZyBDQTEfMB0GA1UEAwwW\nRG9ja2VyIFRlc3QgU2lnbmluZyBDQTAeFw0xNTA1MTMyMzAxMzlaFw0xNzA1MTIy\nMzAxMzlaMGsxEzARBgoJkiaJk/IsZAEZFgNjb20xFjAUBgoJkiaJk/IsZAEZFgZk\nb2NrZXIxEzARBgNVBAoMCkRvY2tlciBJbmMxDDAKBgNVBAsMA2h1YjEZMBcGA1UE\nAwwQaHViZ3cuZG9ja2VyLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBANt4HzdXTDX9yklf5wemrt39peRmdJAHpodYn3PQBhkv8VGEU5+kpV8M9zg5\nj7SSjOED9+3LZ09l6iTBT+dQCJMEgyHN1gHPzkyjklrKfCC6yIKUIt+I/oUoHm3e\nloACrJZXYniZxCpiDf5JeitHCaVxJRGj+MXORe5SVY+V7MLlA3NSDHX6gIhknq59\nBOmSVJyww3Ka54kYtZwz3KZzMdrfvCdkjiCeD1zMJwYX6IU9z746DfHNuMnsH7Di\nmrzhoX/3CMFj9sXdpcrzFSlMDqUxIQunFyN6G4JKZywf9Dvs30Ay6q8gBhursRFd\nx6otDl0z7tqfJkA3e4GSt4/DR7MCAwEAAaOBpDCBoTAOBgNVHQ8BAf8EBAMCBLAw\nCQYDVR0TBAIwADAnBgNVHSUEIDAeBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUF\nBwMDMB0GA1UdDgQWBBQa440jFVFkVPsnGp3sp2bEdtz+6jAfBgNVHSMEGDAWgBQi\nzqX0zdgLeF6GAy7a+zvT4pBCLjAbBgNVHREEFDASghBodWJndy5kb2NrZXIuY29t\nMA0GCSqGSIb3DQEBBQUAA4IBAQB8PdN14gs6zB2m2eB1JpyoFSwho4O1z9UkZqdb\nGUB5SmElVydR2nOMLLUuJoa1zTd1FcX0zlHtDKuRXmO7xaPtrH4Uxiq3Lpu7i2WB\nUDTnMXuzX7hzc5DeU7k7mMbwKRkb+kijJTkET6wcaJwwDSyhzOCZgXW3V0WjHdbD\nprJc52tUk3CHOZY8iZWDVf9gHQpVeJnKzospXJRs3qQksEzD6ciRXu0VHnxeC0DP\n5pnQC17Kkm1QOCHRrsIGDHk9wB1zJlMoeMgIPrCLcC0GappmUlHSMka+3MZYgeW4\nAVlzdia3EYSUuM8Gb5tjSSm/6rTOnXR+zL4PBhNZdAD17hyY\n-----END CERTIFICATE-----'; 253 | set $key '-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbeB83V0w1/cpJ\nX+cHpq7d/aXkZnSQB6aHWJ9z0AYZL/FRhFOfpKVfDPc4OY+0kozhA/fty2dPZeok\nwU/nUAiTBIMhzdYBz85Mo5JaynwgusiClCLfiP6FKB5t3paAAqyWV2J4mcQqYg3+\nSXorRwmlcSURo/jFzkXuUlWPlezC5QNzUgx1+oCIZJ6ufQTpklScsMNymueJGLWc\nM9ymczHa37wnZI4gng9czCcGF+iFPc++Og3xzbjJ7B+w4pq84aF/9wjBY/bF3aXK\n8xUpTA6lMSELpxcjehuCSmcsH/Q77N9AMuqvIAYbq7ERXceqLQ5dM+7anyZAN3uB\nkrePw0ezAgMBAAECggEBAIZga0SYN/qK9ROuG6fsn/8eMjfBn7ccaBNQ6PihM0qy\ntyABVK5XwkWLi8cqP1oBrS6NHn3D3/KWZSGyFzl7IHTb+2p0PIeJdDgqow7iEdR8\naQ7CowOZPrXLFa6R7jZc7M10nb9X7utAdG7xEFN1QGvC9j5x1n1OyjScxvSOiJPf\nQMyR7pAUW3GoNYBdgs8YWmMU7SiS8hp4LRsQ2aJpZUexUVk3o1E3ZIeM/joE+jFZ\nNX+rL0Aqmn0JVgKqZI2CbDB5AwJdSpUYd/m0Ko3E/Hxdu/CbeJqdjSl4ArVIyRBL\nJWY4aMbC81B6ftYGyjpBujXIoyTYNIhf+CYYXzNrsOkCgYEA8J4ajDpKtxowkbIj\nT+9FTY54d+SWcvwEKA22KayyFwx/mXWTnfsylzF6JbGAiZKSG9gbUOP1LsoGDsGj\nhFdLMSfmi151IbOJg1l74E7nM5bqW9ULGyLBOpWlvopXt31jV6uQd/t/+gdflyw0\nMtr642krwfz8UOshkeEIsk+7mmUCgYEA6X/omGsvMyLpa2IWjxwuw+51pCdIoZvL\n7BpV1YHqwO8jWVv1KKpOKzrYZVu93w0WMCHoG/g9FGQtOBmMRk2OKOxmCF+H1or4\nI1+iI/dhTPA2tEeAELX+yGtu4fpEHe+dga1QCNdq06366rn0bhWB5hst1DdVQnXn\n+0KjhLmg7DcCgYAxqQ/lnSpKfBdGGrP7DXEKPrtSU1VRyf25norYMxJWe3fiXkfn\nNS8N0WJaYTYcLqoFIScSHNo/m+aAKSrsZ2/XZ1rHrOkT2ZAqEc/lTaOeHCmmZmPy\nZ8vloXkhyD+uWSylrX0VpkyVd+wcsTzcuiFJyi0Dzojs0nqNNxqqYpZfmQKBgDep\npUIIcyUGkoxlwqj09/T/OI4cS0UzRaaQFJwkL1k06MFZmZTLHH1TtthayWWN0hdB\nTfq076KXyuvPs0/jFxuMVzpxw4kScdrE5nsactiLfw706IOTTxxp9/Ho3iogv/R0\n41poN/AkTmd8UteXSvMW0ZMAadPBFb8hAKgYNFN7AoGAbw6WYYPnhikP7gqWmTh0\n0SOeET5WbRfseiUWMfLGL+R5GmNiBhw63n8srz+KJVLJv0PJ/WKDR7o/Hxjv3w9c\ndkqcFG1MW2NKjrdxztptrRaSD7JyA750rB+CsrwvqzJCvwbulp1iRfWkTqq2fnUU\nQvpxhOYnq/ZlXPjUiE5w67w=\n-----END PRIVATE KEY-----'; 254 | content_by_lua ' 255 | local jwt = require "resty.jwt" 256 | jwt:set_trusted_certs_file("/path/to/cert") 257 | 258 | local jwt_token = jwt:sign( 259 | ngx.var.key, 260 | { 261 | header={ 262 | typ="JWT", 263 | alg="RS256", 264 | x5c={"not a valid certificate", } }, 265 | payload={foo="bar"} 266 | } 267 | ) 268 | 269 | local jwt_obj = jwt:verify(ngx.var.cert, jwt_token) 270 | ngx.say(jwt_obj["verified"]) 271 | ngx.say(jwt_obj["reason"]) 272 | ngx.say(jwt_obj["payload"]["foo"]) 273 | '; 274 | } 275 | --- request 276 | GET /t 277 | --- response_body 278 | false 279 | Malformed x5c header 280 | bar 281 | --- no_error_log 282 | [error] 283 | 284 | 285 | === TEST 10: RS256 unsupported alg 286 | --- http_config eval: $::HttpConfig 287 | --- config 288 | location /t { 289 | content_by_lua ' 290 | local jwt = require "resty.jwt" 291 | 292 | jwt:set_alg_whitelist({RS256=1}) 293 | 294 | local jwt_obj = jwt:verify( 295 | "lua-resty-jwt", 296 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." 297 | .. "eyJmb28iOiJiYXIifQ." 298 | .. "VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY" 299 | ) 300 | ngx.say(jwt_obj["verified"]) 301 | ngx.say(jwt_obj["reason"]) 302 | '; 303 | } 304 | --- request 305 | GET /t 306 | --- response_body 307 | false 308 | whitelist unsupported alg: HS256 309 | --- no_error_log 310 | [error] 311 | 312 | 313 | === TEST 11: JWT sign and verify RS256 - Take 2 314 | --- http_config eval: $::HttpConfig 315 | --- config 316 | location /t { 317 | set $cert '-----BEGIN CERTIFICATE-----\nMIIC2jCCAkMCAg38MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG\nA1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE\nMRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl\nYiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw\nODIyMDUyNzQxWhcNMTcwODIxMDUyNzQxWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE\nCAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs\nZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0z9FeMynsC8+u\ndvX+LciZxnh5uRj4C9S6tNeeAlIGCfQYk0zUcNFCoCkTknNQd/YEiawDLNbxBqut\nbMDZ1aarys1a0lYmUeVLCIqvzBkPJTSQsCopQQ9V8WuT252zzNzs68dVGNdCJd5J\nNRQykpwexmnjPPv0mvj7i8XgG379TyW6P+WWV5okeUkXJ9eJS2ouDYdR2SM9BoVW\n+FgxDu6BmXhozW5EfsnajFp7HL8kQClI0QOc79yuKl3492rH6bzFsFn2lfwWy9ic\n7cP8EpCTeFp1tFaD+vxBhPZkeTQ1HKx6hQ5zeHIB5ySJJZ7af2W8r4eTGYzbdRW2\n4DDHCPhZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQMv+BFvGdMVzkQaQ3/+2noVz\n/uAKbzpEL8xTcxYyP3lkOeh4FoxiSWqy5pGFALdPONoDuYFpLhjJSZaEwuvjI/Tr\nrGhLV1pRG9frwDFshqD2Vaj4ENBCBh6UpeBop5+285zQ4SI7q4U9oSebUDJiuOx6\n+tZ9KynmrbJpTSi0+BM=\n-----END CERTIFICATE-----'; 318 | set $key '-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAtM/RXjMp7AvPrnb1/i3ImcZ4ebkY+AvUurTXngJSBgn0GJNM\n1HDRQqApE5JzUHf2BImsAyzW8QarrWzA2dWmq8rNWtJWJlHlSwiKr8wZDyU0kLAq\nKUEPVfFrk9uds8zc7OvHVRjXQiXeSTUUMpKcHsZp4zz79Jr4+4vF4Bt+/U8luj/l\nlleaJHlJFyfXiUtqLg2HUdkjPQaFVvhYMQ7ugZl4aM1uRH7J2oxaexy/JEApSNED\nnO/cripd+Pdqx+m8xbBZ9pX8FsvYnO3D/BKQk3hadbRWg/r8QYT2ZHk0NRyseoUO\nc3hyAeckiSWe2n9lvK+HkxmM23UVtuAwxwj4WQIDAQABAoIBAE76H0d4La2PEy3v\nhE98DA0vJdx1PzTJZigPacb42H8OxfIeFQcOKDlj381OwNO7MliVEe9pHJG3CjH8\nONhtfBm5wa0UBtFCIFd/6aQUEDYPWECC0kemxV4Sz5yL5vxsVWufKThAW3XnOIrd\nhm74nvzKSeIZ9yvGrU6ipNHY8MUPm0DQVrVYE5MiKjKVExQ4uRAolV2hlmeQDlSt\nk85S0TUOWO1EvJZhsVVs7dBjjY10hIjv3gZPAO8CN85JzMeaNbmWv4RQj0B997in\nrqlOa5qYYt80tAWO4hmPRKCrv6PgThz8C0Cd8AgwNzvQD2d4JpmxxTzBT6/5lRng\nHhj/wQECgYEA2jxC0a4lGmp1q2aYE1Zyiq0UqjxA92pwFYJg3800MLkf96A+dOhd\nwDAc5aAKN8vQV5g33vKi5+pIHWUCskhTS8/PPGrfeqIvtphCj6b7LKosBOhdzrRD\nOsr+Az/SiR2h5l2lr/v7I8I86RTY7MBk4QcRb601kSagWLDNVzSSdhECgYEA1Bm0\n0sByqkQmFoUNRjwmShPfJeVLTCr1G4clljl6MqHmGyRDHxtcp1+CXlyJJemLQY2A\nqrM7/T4x2ta6ME2WgDydFe9M8oU3BbefNYovS6YnoyBqxCx7yZ1vO0Jo40rZI8Bi\nKoCi6e0Hugg4xyPRz9TTNLmr/yEC1qQesMhM9ckCgYEArsT7rfgMdq8zNOSgfTwJ\n1sztc7d1P67ZvCABfLlVRn+6/hAydGVyTus4+RvFkxGB8+RPOhiOJbQVtJSkKCqL\nqnbtu7DK7+ba1xvwkiJjnE1bm0KLfXIXNQpDik6eSHiWo2nzuo/Ne8GeDftIDbG2\nGBAVAp5v+6I3X0+X4nKTqEECgYEAwT4Cj5mjXxnkEdR7eahHwmpEf0RfzC+/Tate\nRXZsrUDwY34wYWEOk7fjEZIBqrcTl1ATEHNojpxh096bmHK4UnHnNRrn4nYY4W6g\n8ajK2oOxzWA1pjJZPiHgO/+PjLafC4G2br7wr2y0A3yGLnmmKVLgc0NPP42WBnVV\nOP/ljnECgYABlDdJCAehDNSv4mdEzY5bfD+VBFd2QsgE1hYhmUYYRNlgIfIL9Y8e\nCduqXFLNZ/LHdmtYembgUqrMiJTUqcbSrJt26kBQx0az3LAV+J2p68PQ85KR9ZPy\nN1jEnRqpAwEdw7S+8l0yVyaNkm66eRI80p+w3AxNbS9hJ/7UlV3lGA==\n-----END RSA PRIVATE KEY-----'; 319 | content_by_lua ' 320 | local jwt = require "resty.jwt" 321 | 322 | local function get_public_key(url, iss, kid) 323 | if iss ~= "Authz" then 324 | error("No issuer. Duh :(") 325 | end 326 | 327 | if kid ~= "IamAPubl1cKeV" then 328 | error("No key identifier. Duh :(") 329 | end 330 | 331 | return ngx.var.cert 332 | end 333 | 334 | jwt:set_trusted_certs_file("/lua-resty-jwt/testcerts/root.pem") 335 | jwt:set_alg_whitelist({ RS256 = 1 }) 336 | jwt:set_x5u_content_retriever(get_public_key) 337 | 338 | local jwt_token = jwt:sign( 339 | ngx.var.key, 340 | { 341 | header={ 342 | typ="JWT", 343 | alg="RS256", 344 | x5u="https://dummy.com/certs", 345 | kid="IamAPubl1cKeV", 346 | }, 347 | payload={foo="bar", iss="Authz", exp=9999999999} 348 | } 349 | ) 350 | 351 | local jwt_obj = jwt:verify(nil, jwt_token) 352 | ngx.say(jwt_obj["verified"]) 353 | ngx.say(jwt_obj["reason"]) 354 | ngx.say(jwt_obj["payload"]["foo"]) 355 | '; 356 | } 357 | --- request 358 | GET /t 359 | --- response_body 360 | true 361 | everything is awesome~ :p 362 | bar 363 | --- no_error_log 364 | [error] 365 | -------------------------------------------------------------------------------- /t/validate-jwt.issuer.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(2); 4 | 5 | plan tests => repeat_each() * (3 * blocks()); 6 | 7 | our $HttpConfig = <<'_EOC_'; 8 | lua_package_path 'lib/?.lua;;'; 9 | _EOC_ 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | === TEST 1: JWT without iss claim without iss requirement 18 | --- http_config eval: $::HttpConfig 19 | --- config 20 | location /t { 21 | content_by_lua ' 22 | local jwt = require "resty.jwt" 23 | local jwt_obj = jwt:verify( 24 | "lua-resty-jwt", 25 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 26 | ".eyJmb28iOiJiYXIifQ" .. 27 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 28 | { } 29 | ) 30 | ngx.say(jwt_obj["verified"]) 31 | ngx.say(jwt_obj["reason"]) 32 | '; 33 | } 34 | --- request 35 | GET /t 36 | --- response_body 37 | true 38 | everything is awesome~ :p 39 | --- no_error_log 40 | [error] 41 | 42 | 43 | === TEST 2: JWT without iss claim with malformed iss requirement 44 | --- http_config eval: $::HttpConfig 45 | --- config 46 | location /t { 47 | content_by_lua ' 48 | local jwt = require "resty.jwt" 49 | local validators = require "resty.jwt-validators" 50 | local jwt_obj = jwt:verify( 51 | "lua-resty-jwt", 52 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 53 | ".eyJmb28iOiJiYXIifQ" .. 54 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 55 | { iss = validators.equals_any_of(17) } 56 | ) 57 | ngx.say(jwt_obj["verified"]) 58 | ngx.say(jwt_obj["reason"]) 59 | '; 60 | } 61 | --- request 62 | GET /t 63 | --- error_code: 500 64 | --- error_log 65 | Cannot create validator for non-table check_values 66 | [error] 67 | 68 | 69 | === TEST 3: JWT without iss claim with malformed iss requirement - Take 2 70 | --- http_config eval: $::HttpConfig 71 | --- config 72 | location /t { 73 | content_by_lua ' 74 | local jwt = require "resty.jwt" 75 | local validators = require "resty.jwt-validators" 76 | local jwt_obj = jwt:verify( 77 | "lua-resty-jwt", 78 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 79 | ".eyJmb28iOiJiYXIifQ" .. 80 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 81 | { iss = validators.equals_any_of({ }) } 82 | ) 83 | ngx.say(jwt_obj["verified"]) 84 | ngx.say(jwt_obj["reason"]) 85 | '; 86 | } 87 | --- request 88 | GET /t 89 | --- error_code: 500 90 | --- error_log 91 | Cannot create validator for empty table check_values 92 | [error] 93 | 94 | 95 | === TEST 4: JWT without iss claim with malformed iss requirement - Take 3 96 | --- http_config eval: $::HttpConfig 97 | --- config 98 | location /t { 99 | content_by_lua ' 100 | local jwt = require "resty.jwt" 101 | local validators = require "resty.jwt-validators" 102 | local jwt_obj = jwt:verify( 103 | "lua-resty-jwt", 104 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 105 | ".eyJmb28iOiJiYXIifQ" .. 106 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 107 | { iss = validators.equals_any_of({ "a", "b", true }) } 108 | ) 109 | ngx.say(jwt_obj["verified"]) 110 | ngx.say(jwt_obj["reason"]) 111 | '; 112 | } 113 | --- request 114 | GET /t 115 | --- error_code: 500 116 | --- error_log 117 | Cannot create validator for non-string table check_values 118 | [error] 119 | 120 | 121 | === TEST 5: JWT without iss claim while iss specified 122 | --- http_config eval: $::HttpConfig 123 | --- config 124 | location /t { 125 | content_by_lua ' 126 | local jwt = require "resty.jwt" 127 | local validators = require "resty.jwt-validators" 128 | local jwt_obj = jwt:verify( 129 | "lua-resty-jwt", 130 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 131 | ".eyJmb28iOiJiYXIifQ" .. 132 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 133 | { iss = validators.equals_any_of({ "a", "b" }) } 134 | ) 135 | ngx.say(jwt_obj["verified"]) 136 | ngx.say(jwt_obj["reason"]) 137 | '; 138 | } 139 | --- request 140 | GET /t 141 | --- response_body 142 | false 143 | 'iss' claim is required. 144 | --- no_error_log 145 | [error] 146 | 147 | 148 | === TEST 6: JWT with malformed iss claim ("iss": 17) while iss specified 149 | --- http_config eval: $::HttpConfig 150 | --- config 151 | location /t { 152 | content_by_lua ' 153 | local jwt = require "resty.jwt" 154 | local validators = require "resty.jwt-validators" 155 | local jwt_obj = jwt:verify( 156 | "lua-resty-jwt", 157 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 158 | ".eyJmb28iOiJiYXIiLCJpc3MiOjE3fQ" .. 159 | ".IYbJt_WGO_2pIM0Mh19HbP5W0y1i9CGw4PNQqjHeIx0", 160 | { iss = validators.equals_any_of({ "a", "b" }) } 161 | ) 162 | ngx.say(jwt_obj["verified"]) 163 | ngx.say(jwt_obj["reason"]) 164 | '; 165 | } 166 | --- request 167 | GET /t 168 | --- response_body 169 | false 170 | 'iss' is malformed. Expected to be a string. 171 | --- no_error_log 172 | [error] 173 | 174 | 175 | === TEST 7: JWT with valid but unknown iss claim ("iss": "hello") while iss specified 176 | --- http_config eval: $::HttpConfig 177 | --- config 178 | location /t { 179 | content_by_lua ' 180 | local jwt = require "resty.jwt" 181 | local validators = require "resty.jwt-validators" 182 | local jwt_obj = jwt:verify( 183 | "lua-resty-jwt", 184 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 185 | ".eyJmb28iOiJiYXIiLCJpc3MiOiJoZWxsbyJ9" .. 186 | ".d8P9QJIJG2LSgQrLOfADw7WqGugRSD3xl-nmZ0FpmC8", 187 | { iss = validators.equals_any_of({ "a", "b" }) } 188 | ) 189 | ngx.say(jwt_obj["verified"]) 190 | ngx.say(jwt_obj["reason"]) 191 | '; 192 | } 193 | --- request 194 | GET /t 195 | --- response_body 196 | false 197 | Claim 'iss' ('hello') returned failure 198 | --- no_error_log 199 | [error] 200 | 201 | 202 | === TEST 8: JWT with valid iss claim ("iss": "hello") while iss specified 203 | --- http_config eval: $::HttpConfig 204 | --- config 205 | location /t { 206 | content_by_lua ' 207 | local jwt = require "resty.jwt" 208 | local validators = require "resty.jwt-validators" 209 | local jwt_obj = jwt:verify( 210 | "lua-resty-jwt", 211 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 212 | ".eyJmb28iOiJiYXIiLCJpc3MiOiJoZWxsbyJ9" .. 213 | ".d8P9QJIJG2LSgQrLOfADw7WqGugRSD3xl-nmZ0FpmC8", 214 | { iss = validators.equals_any_of({ "hello" }) } 215 | ) 216 | ngx.say(jwt_obj["verified"]) 217 | ngx.say(jwt_obj["reason"]) 218 | '; 219 | } 220 | --- request 221 | GET /t 222 | --- response_body 223 | true 224 | everything is awesome~ :p 225 | --- no_error_log 226 | [error] 227 | 228 | 229 | === TEST 9: JWT with valid iss claim ("iss": "hello") while iss specified 230 | --- http_config eval: $::HttpConfig 231 | --- config 232 | location /t { 233 | content_by_lua ' 234 | local jwt = require "resty.jwt" 235 | local validators = require "resty.jwt-validators" 236 | local jwt_obj = jwt:verify( 237 | "lua-resty-jwt", 238 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 239 | ".eyJmb28iOiJiYXIiLCJpc3MiOiJoZWxsbyJ9" .. 240 | ".d8P9QJIJG2LSgQrLOfADw7WqGugRSD3xl-nmZ0FpmC8", 241 | { iss = validators.equals_any_of({ "hello", "a" }) } 242 | ) 243 | ngx.say(jwt_obj["verified"]) 244 | ngx.say(jwt_obj["reason"]) 245 | '; 246 | } 247 | --- request 248 | GET /t 249 | --- response_body 250 | true 251 | everything is awesome~ :p 252 | --- no_error_log 253 | [error] 254 | 255 | 256 | === TEST 10: JWT with valid iss claim ("iss": "hello") while iss specified 257 | --- http_config eval: $::HttpConfig 258 | --- config 259 | location /t { 260 | content_by_lua ' 261 | local jwt = require "resty.jwt" 262 | local validators = require "resty.jwt-validators" 263 | local jwt_obj = jwt:verify( 264 | "lua-resty-jwt", 265 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 266 | ".eyJmb28iOiJiYXIiLCJpc3MiOiJoZWxsbyJ9" .. 267 | ".d8P9QJIJG2LSgQrLOfADw7WqGugRSD3xl-nmZ0FpmC8", 268 | { iss = validators.equals_any_of({ "a", "hello" }) } 269 | ) 270 | ngx.say(jwt_obj["verified"]) 271 | ngx.say(jwt_obj["reason"]) 272 | '; 273 | } 274 | --- request 275 | GET /t 276 | --- response_body 277 | true 278 | everything is awesome~ :p 279 | --- no_error_log 280 | [error] 281 | -------------------------------------------------------------------------------- /t/validate-jwt.lifecycle.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(2); 4 | 5 | plan tests => repeat_each() * (3 * blocks()); 6 | 7 | our $HttpConfig = <<'_EOC_'; 8 | lua_package_path 'lib/?.lua;;'; 9 | _EOC_ 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | 18 | === TEST 1: JWT with invalid exp ("exp": "17") 19 | --- http_config eval: $::HttpConfig 20 | --- config 21 | location /t { 22 | content_by_lua ' 23 | local jwt = require "resty.jwt" 24 | local validators = require "resty.jwt-validators" 25 | validators.set_system_leeway(0) 26 | local jwt_obj = jwt:verify( 27 | "lua-resty-jwt", 28 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 29 | ".eyJmb28iOiJiYXIiLCJleHAiOiIxNyJ9" .. 30 | ".6gWBliIuNT1qF_RhD1ymI-zRyN38zGme0dHvYkOFgxM", 31 | { 32 | __jwt = validators.require_one_of({ "nbf", "exp" }), 33 | nbf = validators.opt_is_not_before(), 34 | exp = validators.opt_is_not_expired() 35 | } 36 | ) 37 | ngx.say(jwt_obj["verified"]) 38 | ngx.say(jwt_obj["reason"]) 39 | '; 40 | } 41 | --- request 42 | GET /t 43 | --- response_body 44 | false 45 | 'exp' is malformed. Expected to be a positive numeric value. 46 | --- no_error_log 47 | [error] 48 | 49 | 50 | === TEST 2: JWT with invalid exp ("exp": -17) 51 | --- http_config eval: $::HttpConfig 52 | --- config 53 | location /t { 54 | content_by_lua ' 55 | local jwt = require "resty.jwt" 56 | local validators = require "resty.jwt-validators" 57 | validators.set_system_leeway(0) 58 | local jwt_obj = jwt:verify( 59 | "lua-resty-jwt", 60 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 61 | ".eyJmb28iOiJiYXIiLCJleHAiOi0xN30" .. 62 | ".Jd3_eeMBJeWAeyke5SbXD3TecVPpci7lNLWGze9OP9o", 63 | { 64 | __jwt = validators.require_one_of({ "nbf", "exp" }), 65 | nbf = validators.opt_is_not_before(), 66 | exp = validators.opt_is_not_expired() 67 | } 68 | ) 69 | ngx.say(jwt_obj["verified"]) 70 | ngx.say(jwt_obj["reason"]) 71 | '; 72 | } 73 | --- request 74 | GET /t 75 | --- response_body 76 | false 77 | 'exp' is malformed. Expected to be a positive numeric value. 78 | --- no_error_log 79 | [error] 80 | 81 | 82 | === TEST 3: JWT with invalid nbf ("nbf": "17") 83 | --- http_config eval: $::HttpConfig 84 | --- config 85 | location /t { 86 | content_by_lua ' 87 | local jwt = require "resty.jwt" 88 | local validators = require "resty.jwt-validators" 89 | validators.set_system_leeway(0) 90 | local jwt_obj = jwt:verify( 91 | "lua-resty-jwt", 92 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 93 | ".eyJmb28iOiJiYXIiLCJuYmYiOiIxNyJ9" .. 94 | ".kYzPvYDRiW37rsdYNfFd57KDBuZpm1loCRIJSUlQjbE", 95 | { 96 | __jwt = validators.require_one_of({ "nbf", "exp" }), 97 | nbf = validators.opt_is_not_before(), 98 | exp = validators.opt_is_not_expired() 99 | } 100 | ) 101 | ngx.say(jwt_obj["verified"]) 102 | ngx.say(jwt_obj["reason"]) 103 | '; 104 | } 105 | --- request 106 | GET /t 107 | --- response_body 108 | false 109 | 'nbf' is malformed. Expected to be a positive numeric value. 110 | --- no_error_log 111 | [error] 112 | 113 | 114 | === TEST 4: JWT with invalid nbf ("nbf": -17) 115 | --- http_config eval: $::HttpConfig 116 | --- config 117 | location /t { 118 | content_by_lua ' 119 | local jwt = require "resty.jwt" 120 | local validators = require "resty.jwt-validators" 121 | validators.set_system_leeway(0) 122 | local jwt_obj = jwt:verify( 123 | "lua-resty-jwt", 124 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 125 | ".eyJmb28iOiJiYXIiLCJuYmYiOi0xN30" .. 126 | ".jNUyAIYISmDcemGO3gE17byPZ_ZO-WZxaMt59UNslPc", 127 | { 128 | __jwt = validators.require_one_of({ "nbf", "exp" }), 129 | nbf = validators.opt_is_not_before(), 130 | exp = validators.opt_is_not_expired() 131 | } 132 | ) 133 | ngx.say(jwt_obj["verified"]) 134 | ngx.say(jwt_obj["reason"]) 135 | '; 136 | } 137 | --- request 138 | GET /t 139 | --- response_body 140 | false 141 | 'nbf' is malformed. Expected to be a positive numeric value. 142 | --- no_error_log 143 | [error] 144 | 145 | 146 | === TEST 5: JWT with invalid negative lifetime grace period 147 | --- http_config eval: $::HttpConfig 148 | --- config 149 | location /t { 150 | content_by_lua ' 151 | local jwt = require "resty.jwt" 152 | local validators = require "resty.jwt-validators" 153 | validators.set_system_leeway(-1) 154 | local jwt_obj = jwt:verify( 155 | "lua-resty-jwt", 156 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 157 | ".eyJmb28iOiJiYXIifQ" .. 158 | ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY", 159 | { 160 | __jwt = validators.require_one_of({ "nbf", "exp" }), 161 | nbf = validators.opt_is_not_before(), 162 | exp = validators.opt_is_not_expired() 163 | } 164 | ) 165 | ngx.say(jwt_obj["verified"]) 166 | ngx.say(jwt_obj["reason"]) 167 | '; 168 | } 169 | --- request 170 | GET /t 171 | --- error_code: 500 172 | --- error_log 173 | leeway must be a non-negative number 174 | [error] 175 | 176 | 177 | === TEST 6: JWT with invalid alpha lifetime grace period 178 | --- http_config eval: $::HttpConfig 179 | --- config 180 | location /t { 181 | content_by_lua ' 182 | local jwt = require "resty.jwt" 183 | local validators = require "resty.jwt-validators" 184 | validators.set_system_leeway("boom ?") 185 | local jwt_obj = jwt:verify( 186 | "lua-resty-jwt", 187 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 188 | ".eyJmb28iOiJiYXIifQ" .. 189 | ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY", 190 | { 191 | __jwt = validators.require_one_of({ "nbf", "exp" }), 192 | nbf = validators.opt_is_not_before(), 193 | exp = validators.opt_is_not_expired() 194 | } 195 | ) 196 | ngx.say(jwt_obj["verified"]) 197 | ngx.say(jwt_obj["reason"]) 198 | '; 199 | } 200 | --- request 201 | GET /t 202 | --- error_code: 500 203 | --- error_log 204 | leeway must be a non-negative number 205 | [error] 206 | 207 | 208 | === TEST 7: JWT with no lifetime grace period and valid exp ("exp": 9999999999) 209 | --- http_config eval: $::HttpConfig 210 | --- config 211 | location /t { 212 | content_by_lua ' 213 | local jwt = require "resty.jwt" 214 | local validators = require "resty.jwt-validators" 215 | validators.set_system_leeway(0) 216 | local jwt_obj = jwt:verify( 217 | "lua-resty-jwt", 218 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 219 | ".eyJmb28iOiJiYXIiLCJleHAiOjk5OTk5OTk5OTl9" .. 220 | ".Y503HYultweqOpvvNF3fj2FTb_rH7ZwKAXap6cPqXjw", 221 | { 222 | __jwt = validators.require_one_of({ "nbf", "exp" }), 223 | nbf = validators.opt_is_not_before(), 224 | exp = validators.opt_is_not_expired() 225 | } 226 | ) 227 | ngx.say(jwt_obj["verified"]) 228 | ngx.say(jwt_obj["reason"]) 229 | '; 230 | } 231 | --- request 232 | GET /t 233 | --- response_body 234 | true 235 | everything is awesome~ :p 236 | --- no_error_log 237 | [error] 238 | 239 | 240 | === TEST 8: JWT with no lifetime grace period and invalid exp ("exp": 0) 241 | --- http_config eval: $::HttpConfig 242 | --- config 243 | location /t { 244 | content_by_lua ' 245 | local jwt = require "resty.jwt" 246 | local validators = require "resty.jwt-validators" 247 | validators.set_system_leeway(0) 248 | local jwt_obj = jwt:verify( 249 | "lua-resty-jwt", 250 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 251 | ".eyJmb28iOiJiYXIiLCJleHAiOjB9" .. 252 | ".btivkb1guN1sQBYYVcrigEuNVvDOp1PDrbgaNSD3Whg", 253 | { 254 | __jwt = validators.require_one_of({ "nbf", "exp" }), 255 | nbf = validators.opt_is_not_before(), 256 | exp = validators.opt_is_not_expired() 257 | } 258 | ) 259 | ngx.say(jwt_obj["verified"]) 260 | ngx.say(jwt_obj["reason"]) 261 | '; 262 | } 263 | --- request 264 | GET /t 265 | --- response_body 266 | false 267 | 'exp' claim expired at Thu, 01 Jan 1970 00:00:00 GMT 268 | --- no_error_log 269 | [error] 270 | 271 | 272 | === TEST 9: JWT with no lifetime grace period and valid nbf ("nbf": 0) 273 | --- http_config eval: $::HttpConfig 274 | --- config 275 | location /t { 276 | content_by_lua ' 277 | local jwt = require "resty.jwt" 278 | local validators = require "resty.jwt-validators" 279 | validators.set_system_leeway(0) 280 | local jwt_obj = jwt:verify( 281 | "lua-resty-jwt", 282 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 283 | ".eyJmb28iOiJiYXIiLCJuYmYiOjB9" .. 284 | ".qZeWRQBHZhRcszwbiL7JV6Nf-irT75u4IHhoQBTqkzo", 285 | { 286 | __jwt = validators.require_one_of({ "nbf", "exp" }), 287 | nbf = validators.opt_is_not_before(), 288 | exp = validators.opt_is_not_expired() 289 | } 290 | ) 291 | ngx.say(jwt_obj["verified"]) 292 | ngx.say(jwt_obj["reason"]) 293 | '; 294 | } 295 | --- request 296 | GET /t 297 | --- response_body 298 | true 299 | everything is awesome~ :p 300 | --- no_error_log 301 | [error] 302 | 303 | 304 | === TEST 10: JWT with no lifetime grace period and invalid nbf ("nbf": 9999999999) 305 | --- http_config eval: $::HttpConfig 306 | --- config 307 | location /t { 308 | content_by_lua ' 309 | local jwt = require "resty.jwt" 310 | local validators = require "resty.jwt-validators" 311 | validators.set_system_leeway(0) 312 | local jwt_obj = jwt:verify( 313 | "lua-resty-jwt", 314 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 315 | ".eyJmb28iOiJiYXIiLCJuYmYiOjk5OTk5OTk5OTl9" .. 316 | ".Wfu3owxbzlrb0GXvV0D22Si8WEDP0WeRGwZNPAoYHMI", 317 | { 318 | __jwt = validators.require_one_of({ "nbf", "exp" }), 319 | nbf = validators.opt_is_not_before(), 320 | exp = validators.opt_is_not_expired() 321 | } 322 | ) 323 | ngx.say(jwt_obj["verified"]) 324 | ngx.say(jwt_obj["reason"]) 325 | '; 326 | } 327 | --- request 328 | GET /t 329 | --- response_body 330 | false 331 | 'nbf' claim not valid until Sat, 20 Nov 2286 17:46:39 GMT 332 | --- no_error_log 333 | [error] 334 | 335 | 336 | === TEST 11: JWT with super large lifetime grace period and invalid nbf ("nbf": 9999999999) 337 | --- http_config eval: $::HttpConfig 338 | --- config 339 | location /t { 340 | content_by_lua ' 341 | local jwt = require "resty.jwt" 342 | local validators = require "resty.jwt-validators" 343 | validators.set_system_leeway(9999999999) 344 | local jwt_obj = jwt:verify( 345 | "lua-resty-jwt", 346 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 347 | ".eyJmb28iOiJiYXIiLCJuYmYiOjk5OTk5OTk5OTl9" .. 348 | ".Wfu3owxbzlrb0GXvV0D22Si8WEDP0WeRGwZNPAoYHMI", 349 | { 350 | __jwt = validators.require_one_of({ "nbf", "exp" }), 351 | nbf = validators.opt_is_not_before(), 352 | exp = validators.opt_is_not_expired() 353 | } 354 | ) 355 | ngx.say(jwt_obj["verified"]) 356 | ngx.say(jwt_obj["reason"]) 357 | '; 358 | } 359 | --- request 360 | GET /t 361 | --- response_body 362 | true 363 | everything is awesome~ :p 364 | --- no_error_log 365 | [error] 366 | 367 | 368 | === TEST 12: JWT with super large lifetime grace period and invalid exp ("exp": 0) 369 | --- http_config eval: $::HttpConfig 370 | --- config 371 | location /t { 372 | content_by_lua ' 373 | local jwt = require "resty.jwt" 374 | local validators = require "resty.jwt-validators" 375 | validators.set_system_leeway(9999999999) 376 | local jwt_obj = jwt:verify( 377 | "lua-resty-jwt", 378 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 379 | ".eyJmb28iOiJiYXIiLCJleHAiOjB9" .. 380 | ".btivkb1guN1sQBYYVcrigEuNVvDOp1PDrbgaNSD3Whg", 381 | { 382 | __jwt = validators.require_one_of({ "nbf", "exp" }), 383 | nbf = validators.opt_is_not_before(), 384 | exp = validators.opt_is_not_expired() 385 | } 386 | ) 387 | ngx.say(jwt_obj["verified"]) 388 | ngx.say(jwt_obj["reason"]) 389 | '; 390 | } 391 | --- request 392 | GET /t 393 | --- response_body 394 | true 395 | everything is awesome~ :p 396 | --- no_error_log 397 | [error] 398 | 399 | 400 | === TEST 13: JWT without exp nor nbf claim without lifetime related requirement 401 | --- http_config eval: $::HttpConfig 402 | --- config 403 | location /t { 404 | content_by_lua ' 405 | local jwt = require "resty.jwt" 406 | local jwt_obj = jwt:verify( 407 | "lua-resty-jwt", 408 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 409 | ".eyJmb28iOiJiYXIifQ" .. 410 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 411 | { } 412 | ) 413 | ngx.say(jwt_obj["verified"]) 414 | ngx.say(jwt_obj["reason"]) 415 | '; 416 | } 417 | --- request 418 | GET /t 419 | --- response_body 420 | true 421 | everything is awesome~ :p 422 | --- no_error_log 423 | [error] 424 | 425 | 426 | === TEST 14: JWT without exp nor nbf claim without lifetime related requirement - Take 2 427 | --- http_config eval: $::HttpConfig 428 | --- config 429 | location /t { 430 | content_by_lua ' 431 | local jwt = require "resty.jwt" 432 | local validators = require "resty.jwt-validators" 433 | local jwt_obj = jwt:verify( 434 | "lua-resty-jwt", 435 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 436 | ".eyJmb28iOiJiYXIifQ" .. 437 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 438 | { 439 | nbf = validators.opt_is_not_before(), 440 | exp = validators.opt_is_not_expired() 441 | } 442 | ) 443 | ngx.say(jwt_obj["verified"]) 444 | ngx.say(jwt_obj["reason"]) 445 | '; 446 | } 447 | --- request 448 | GET /t 449 | --- response_body 450 | true 451 | everything is awesome~ :p 452 | --- no_error_log 453 | [error] 454 | 455 | 456 | === TEST 15: JWT without exp nor nbf claim without lifetime related requirement - Take 3 457 | --- http_config eval: $::HttpConfig 458 | --- config 459 | location /t { 460 | content_by_lua ' 461 | local jwt = require "resty.jwt" 462 | local validators = require "resty.jwt-validators" 463 | local jwt_obj = jwt:verify( 464 | "lua-resty-jwt", 465 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 466 | ".eyJmb28iOiJiYXIifQ" .. 467 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 468 | { 469 | nbf = validators.opt_is_not_before(), 470 | exp = validators.opt_is_not_expired() 471 | } 472 | ) 473 | ngx.say(jwt_obj["verified"]) 474 | ngx.say(jwt_obj["reason"]) 475 | '; 476 | } 477 | --- request 478 | GET /t 479 | --- response_body 480 | true 481 | everything is awesome~ :p 482 | --- no_error_log 483 | [error] 484 | 485 | 486 | === TEST 16: JWT without exp nor nbf claim while lifetime grace period specified 487 | --- http_config eval: $::HttpConfig 488 | --- config 489 | location /t { 490 | content_by_lua ' 491 | local jwt = require "resty.jwt" 492 | local validators = require "resty.jwt-validators" 493 | validators.set_system_leeway(1) 494 | local jwt_obj = jwt:verify( 495 | "lua-resty-jwt", 496 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 497 | ".eyJmb28iOiJiYXIifQ" .. 498 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 499 | { 500 | __jwt = validators.require_one_of({ "nbf", "exp" }), 501 | nbf = validators.opt_is_not_before(), 502 | exp = validators.opt_is_not_expired() 503 | } 504 | ) 505 | ngx.say(jwt_obj["verified"]) 506 | ngx.say(jwt_obj["reason"]) 507 | '; 508 | } 509 | --- request 510 | GET /t 511 | --- response_body 512 | false 513 | Missing one of claims - [ nbf, exp ]. 514 | --- no_error_log 515 | [error] 516 | 517 | 518 | === TEST 17: JWT without exp nor nbf claim while lifetime grace period specified - Take 2 519 | --- http_config eval: $::HttpConfig 520 | --- config 521 | location /t { 522 | content_by_lua ' 523 | local jwt = require "resty.jwt" 524 | local validators = require "resty.jwt-validators" 525 | validators.set_system_leeway(0) 526 | local jwt_obj = jwt:verify( 527 | "lua-resty-jwt", 528 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 529 | ".eyJmb28iOiJiYXIifQ" .. 530 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 531 | { 532 | __jwt = validators.require_one_of({ "nbf", "exp" }), 533 | nbf = validators.opt_is_not_before(), 534 | exp = validators.opt_is_not_expired() 535 | } 536 | ) 537 | ngx.say(jwt_obj["verified"]) 538 | ngx.say(jwt_obj["reason"]) 539 | '; 540 | } 541 | --- request 542 | GET /t 543 | --- response_body 544 | false 545 | Missing one of claims - [ nbf, exp ]. 546 | --- no_error_log 547 | [error] 548 | 549 | 550 | === TEST 18: JWT without exp nor nbf claim while exp claim required 551 | --- http_config eval: $::HttpConfig 552 | --- config 553 | location /t { 554 | content_by_lua ' 555 | local jwt = require "resty.jwt" 556 | local validators = require "resty.jwt-validators" 557 | local jwt_obj = jwt:verify( 558 | "lua-resty-jwt", 559 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 560 | ".eyJmb28iOiJiYXIifQ" .. 561 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 562 | { 563 | nbf = validators.opt_is_not_before(), 564 | exp = validators.is_not_expired() 565 | } 566 | ) 567 | ngx.say(jwt_obj["verified"]) 568 | ngx.say(jwt_obj["reason"]) 569 | '; 570 | } 571 | --- request 572 | GET /t 573 | --- response_body 574 | false 575 | 'exp' claim is required. 576 | --- no_error_log 577 | [error] 578 | 579 | 580 | === TEST 19: JWT without exp nor nbf claim while nbf claim required 581 | --- http_config eval: $::HttpConfig 582 | --- config 583 | location /t { 584 | content_by_lua ' 585 | local jwt = require "resty.jwt" 586 | local validators = require "resty.jwt-validators" 587 | local jwt_obj = jwt:verify( 588 | "lua-resty-jwt", 589 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 590 | ".eyJmb28iOiJiYXIifQ" .. 591 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 592 | { 593 | nbf = validators.is_not_before(), 594 | exp = validators.opt_is_not_expired() 595 | } 596 | ) 597 | ngx.say(jwt_obj["verified"]) 598 | ngx.say(jwt_obj["reason"]) 599 | '; 600 | } 601 | --- request 602 | GET /t 603 | --- response_body 604 | false 605 | 'nbf' claim is required. 606 | --- no_error_log 607 | [error] 608 | 609 | 610 | === TEST 20: JWT with valid exp ("exp": 9999999999) while exp claim required 611 | --- http_config eval: $::HttpConfig 612 | --- config 613 | location /t { 614 | content_by_lua ' 615 | local jwt = require "resty.jwt" 616 | local validators = require "resty.jwt-validators" 617 | local jwt_obj = jwt:verify( 618 | "lua-resty-jwt", 619 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 620 | ".eyJmb28iOiJiYXIiLCJleHAiOjk5OTk5OTk5OTl9" .. 621 | ".Y503HYultweqOpvvNF3fj2FTb_rH7ZwKAXap6cPqXjw", 622 | { 623 | nbf = validators.opt_is_not_before(), 624 | exp = validators.is_not_expired() 625 | } 626 | ) 627 | ngx.say(jwt_obj["verified"]) 628 | ngx.say(jwt_obj["reason"]) 629 | '; 630 | } 631 | --- request 632 | GET /t 633 | --- response_body 634 | true 635 | everything is awesome~ :p 636 | --- no_error_log 637 | [error] 638 | 639 | 640 | === TEST 21: JWT with valid exp ("exp": 9999999999) while nbf claim required 641 | --- http_config eval: $::HttpConfig 642 | --- config 643 | location /t { 644 | content_by_lua ' 645 | local jwt = require "resty.jwt" 646 | local validators = require "resty.jwt-validators" 647 | local jwt_obj = jwt:verify( 648 | "lua-resty-jwt", 649 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 650 | ".eyJmb28iOiJiYXIiLCJleHAiOjk5OTk5OTk5OTl9" .. 651 | ".Y503HYultweqOpvvNF3fj2FTb_rH7ZwKAXap6cPqXjw", 652 | { 653 | nbf = validators.is_not_before(), 654 | exp = validators.opt_is_not_expired() 655 | } 656 | ) 657 | ngx.say(jwt_obj["verified"]) 658 | ngx.say(jwt_obj["reason"]) 659 | '; 660 | } 661 | --- request 662 | GET /t 663 | --- response_body 664 | false 665 | 'nbf' claim is required. 666 | --- no_error_log 667 | [error] 668 | 669 | 670 | === TEST 22: JWT with valid nbf ("nbf": 0) while nbf claim required 671 | --- http_config eval: $::HttpConfig 672 | --- config 673 | location /t { 674 | content_by_lua ' 675 | local jwt = require "resty.jwt" 676 | local validators = require "resty.jwt-validators" 677 | local jwt_obj = jwt:verify( 678 | "lua-resty-jwt", 679 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 680 | ".eyJmb28iOiJiYXIiLCJuYmYiOjB9" .. 681 | ".qZeWRQBHZhRcszwbiL7JV6Nf-irT75u4IHhoQBTqkzo", 682 | { 683 | nbf = validators.is_not_before(), 684 | exp = validators.opt_is_not_expired() 685 | } 686 | ) 687 | ngx.say(jwt_obj["verified"]) 688 | ngx.say(jwt_obj["reason"]) 689 | '; 690 | } 691 | --- request 692 | GET /t 693 | --- response_body 694 | true 695 | everything is awesome~ :p 696 | --- no_error_log 697 | [error] 698 | 699 | 700 | === TEST 23: JWT with valid nbf ("nbf": 0) while exp claim required 701 | --- http_config eval: $::HttpConfig 702 | --- config 703 | location /t { 704 | content_by_lua ' 705 | local jwt = require "resty.jwt" 706 | local validators = require "resty.jwt-validators" 707 | local jwt_obj = jwt:verify( 708 | "lua-resty-jwt", 709 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 710 | ".eyJmb28iOiJiYXIiLCJuYmYiOjB9" .. 711 | ".qZeWRQBHZhRcszwbiL7JV6Nf-irT75u4IHhoQBTqkzo", 712 | { 713 | nbf = validators.opt_is_not_before(), 714 | exp = validators.is_not_expired() 715 | } 716 | ) 717 | ngx.say(jwt_obj["verified"]) 718 | ngx.say(jwt_obj["reason"]) 719 | '; 720 | } 721 | --- request 722 | GET /t 723 | --- response_body 724 | false 725 | 'exp' claim is required. 726 | --- no_error_log 727 | [error] 728 | -------------------------------------------------------------------------------- /t/validate-jwt.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(2); 4 | 5 | plan tests => repeat_each() * (3 * blocks()); 6 | 7 | our $HttpConfig = <<'_EOC_'; 8 | lua_package_path 'lib/?.lua;;'; 9 | _EOC_ 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | === TEST 1: JWT without sub claim and without claim requirement 18 | --- http_config eval: $::HttpConfig 19 | --- config 20 | location /t { 21 | content_by_lua ' 22 | local jwt = require "resty.jwt" 23 | local jwt_obj = jwt:verify( 24 | "lua-resty-jwt", 25 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 26 | ".eyJmb28iOiJiYXIifQ" .. 27 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 28 | { } 29 | ) 30 | ngx.say(jwt_obj["verified"]) 31 | ngx.say(jwt_obj["reason"]) 32 | '; 33 | } 34 | --- request 35 | GET /t 36 | --- response_body 37 | true 38 | everything is awesome~ :p 39 | --- no_error_log 40 | [error] 41 | 42 | 43 | === TEST 2: JWT with sub claim and with exact string claim requirement 44 | --- http_config eval: $::HttpConfig 45 | --- config 46 | location /t { 47 | content_by_lua ' 48 | local jwt = require "resty.jwt" 49 | local validators = require "resty.jwt-validators" 50 | local jwt_obj = jwt:verify( 51 | "lua-resty-jwt", 52 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 53 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 54 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 55 | { 56 | sub = validators.equals("Test Subject") 57 | } 58 | ) 59 | ngx.say(jwt_obj["verified"]) 60 | ngx.say(jwt_obj["reason"]) 61 | '; 62 | } 63 | --- request 64 | GET /t 65 | --- response_body 66 | true 67 | everything is awesome~ :p 68 | --- no_error_log 69 | [error] 70 | 71 | 72 | === TEST 3: JWT with sub claim and with pattern matching string claim requirement 73 | --- http_config eval: $::HttpConfig 74 | --- config 75 | location /t { 76 | content_by_lua ' 77 | local jwt = require "resty.jwt" 78 | local validators = require "resty.jwt-validators" 79 | local jwt_obj = jwt:verify( 80 | "lua-resty-jwt", 81 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 82 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 83 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 84 | { 85 | sub = validators.matches("^Test [a-zA-Z]+$") 86 | } 87 | ) 88 | ngx.say(jwt_obj["verified"]) 89 | ngx.say(jwt_obj["reason"]) 90 | '; 91 | } 92 | --- request 93 | GET /t 94 | --- response_body 95 | true 96 | everything is awesome~ :p 97 | --- no_error_log 98 | [error] 99 | 100 | 101 | === TEST 4: JWT with sub claim and with non-anchored matching string claim requirement 102 | --- http_config eval: $::HttpConfig 103 | --- config 104 | location /t { 105 | content_by_lua ' 106 | local jwt = require "resty.jwt" 107 | local validators = require "resty.jwt-validators" 108 | local jwt_obj = jwt:verify( 109 | "lua-resty-jwt", 110 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 111 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 112 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 113 | { 114 | sub = validators.matches("st Sub") 115 | } 116 | ) 117 | ngx.say(jwt_obj["verified"]) 118 | ngx.say(jwt_obj["reason"]) 119 | '; 120 | } 121 | --- request 122 | GET /t 123 | --- response_body 124 | true 125 | everything is awesome~ :p 126 | --- no_error_log 127 | [error] 128 | 129 | 130 | === TEST 5: JWT with sub claim and with non-matching string claim requirement 131 | --- http_config eval: $::HttpConfig 132 | --- config 133 | location /t { 134 | content_by_lua ' 135 | local jwt = require "resty.jwt" 136 | local validators = require "resty.jwt-validators" 137 | local jwt_obj = jwt:verify( 138 | "lua-resty-jwt", 139 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 140 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 141 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 142 | { 143 | sub = validators.equals("Some Other") 144 | } 145 | ) 146 | ngx.say(jwt_obj["verified"]) 147 | ngx.say(jwt_obj["reason"]) 148 | '; 149 | } 150 | --- request 151 | GET /t 152 | --- response_body 153 | false 154 | Claim 'sub' ('Test Subject') returned failure 155 | --- no_error_log 156 | [error] 157 | 158 | 159 | === TEST 6: JWT with sub claim and with matching function claim requirement 160 | --- http_config eval: $::HttpConfig 161 | --- config 162 | location /t { 163 | content_by_lua ' 164 | local jwt = require "resty.jwt" 165 | local validators = require "resty.jwt-validators" 166 | local jwt_obj = jwt:verify( 167 | "lua-resty-jwt", 168 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 169 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 170 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 171 | { 172 | sub = validators.required(function(val) 173 | ngx.say("Checking " .. val) 174 | if val ~= "Test Subject" then 175 | error(val .. " does not pass function") 176 | end 177 | end) 178 | } 179 | ) 180 | ngx.say(jwt_obj["verified"]) 181 | ngx.say(jwt_obj["reason"]) 182 | '; 183 | } 184 | --- request 185 | GET /t 186 | --- response_body 187 | Checking Test Subject 188 | true 189 | everything is awesome~ :p 190 | --- no_error_log 191 | [error] 192 | 193 | 194 | === TEST 7: JWT with sub claim and with non-matching function claim requirement 195 | --- http_config eval: $::HttpConfig 196 | --- config 197 | location /t { 198 | content_by_lua ' 199 | local jwt = require "resty.jwt" 200 | local validators = require "resty.jwt-validators" 201 | local jwt_obj = jwt:verify( 202 | "lua-resty-jwt", 203 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 204 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 205 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 206 | { 207 | sub = validators.required(function(val) 208 | ngx.say("Checking " .. val) 209 | if val ~= "Some Other" then 210 | error({ reason = val .. " does not pass function" }) 211 | end 212 | end) 213 | } 214 | ) 215 | ngx.say(jwt_obj["verified"]) 216 | ngx.say(jwt_obj["reason"]) 217 | '; 218 | } 219 | --- request 220 | GET /t 221 | --- response_body 222 | Checking Test Subject 223 | false 224 | Test Subject does not pass function 225 | --- no_error_log 226 | [error] 227 | 228 | 229 | === TEST 8: JWT without sub claim and with string claim requirement 230 | --- http_config eval: $::HttpConfig 231 | --- config 232 | location /t { 233 | content_by_lua ' 234 | local jwt = require "resty.jwt" 235 | local validators = require "resty.jwt-validators" 236 | local jwt_obj = jwt:verify( 237 | "lua-resty-jwt", 238 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 239 | ".eyJmb28iOiJiYXIifQ" .. 240 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 241 | { 242 | sub = validators.equals("Test Subject") 243 | } 244 | ) 245 | ngx.say(jwt_obj["verified"]) 246 | ngx.say(jwt_obj["reason"]) 247 | '; 248 | } 249 | --- request 250 | GET /t 251 | --- response_body 252 | false 253 | 'sub' claim is required. 254 | --- no_error_log 255 | [error] 256 | 257 | 258 | === TEST 9: JWT without sub claim and with function claim requirement 259 | --- http_config eval: $::HttpConfig 260 | --- config 261 | location /t { 262 | content_by_lua ' 263 | local jwt = require "resty.jwt" 264 | local validators = require "resty.jwt-validators" 265 | local jwt_obj = jwt:verify( 266 | "lua-resty-jwt", 267 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 268 | ".eyJmb28iOiJiYXIifQ" .. 269 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 270 | { 271 | sub = validators.required(function(val) 272 | ngx.say("Checking " .. val) 273 | if val ~= "Test Subject" then 274 | error({ reason = val .. " does not pass function" }) 275 | end 276 | end) 277 | } 278 | ) 279 | ngx.say(jwt_obj["verified"]) 280 | ngx.say(jwt_obj["reason"]) 281 | '; 282 | } 283 | --- request 284 | GET /t 285 | --- response_body 286 | false 287 | 'sub' claim is required. 288 | --- no_error_log 289 | [error] 290 | 291 | 292 | === TEST 10: JWT with sub claim and with invalid claim requirement 293 | --- http_config eval: $::HttpConfig 294 | --- config 295 | location /t { 296 | content_by_lua ' 297 | local jwt = require "resty.jwt" 298 | local success, err = pcall(function () jwt:verify( 299 | "lua-resty-jwt", 300 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 301 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 302 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 303 | { 304 | sub = true 305 | } 306 | ) end) 307 | err = string.gsub(err, "^.*: ", "") 308 | ngx.say(err) 309 | '; 310 | } 311 | --- request 312 | GET /t 313 | --- response_body 314 | Claim spec value must be a function - see jwt-validators.lua for helper functions 315 | --- no_error_log 316 | [error] 317 | 318 | 319 | === TEST 11: JWT with sub claim and with invalid (string) claim requirement 320 | --- http_config eval: $::HttpConfig 321 | --- config 322 | location /t { 323 | content_by_lua ' 324 | local jwt = require "resty.jwt" 325 | local success, err = pcall(function () jwt:verify( 326 | "lua-resty-jwt", 327 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 328 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 329 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 330 | { 331 | sub = "abc" 332 | } 333 | ) end) 334 | err = string.gsub(err, "^.*: ", "") 335 | ngx.say(err) 336 | '; 337 | } 338 | --- request 339 | GET /t 340 | --- response_body 341 | Claim spec value must be a function - see jwt-validators.lua for helper functions 342 | --- no_error_log 343 | [error] 344 | 345 | 346 | === TEST 12: JWT with sub claim and with function returning true 347 | --- http_config eval: $::HttpConfig 348 | --- config 349 | location /t { 350 | content_by_lua ' 351 | local jwt = require "resty.jwt" 352 | local validators = require "resty.jwt-validators" 353 | local jwt_obj = jwt:verify( 354 | "lua-resty-jwt", 355 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 356 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 357 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 358 | { 359 | sub = validators.required(function(val) 360 | return val == "Test Subject" and true or false 361 | end) 362 | } 363 | ) 364 | ngx.say(jwt_obj["verified"]) 365 | ngx.say(jwt_obj["reason"]) 366 | '; 367 | } 368 | --- request 369 | GET /t 370 | --- response_body 371 | true 372 | everything is awesome~ :p 373 | --- no_error_log 374 | [error] 375 | 376 | 377 | === TEST 13: JWT with sub claim and with function returning false 378 | --- http_config eval: $::HttpConfig 379 | --- config 380 | location /t { 381 | content_by_lua ' 382 | local jwt = require "resty.jwt" 383 | local validators = require "resty.jwt-validators" 384 | local jwt_obj = jwt:verify( 385 | "lua-resty-jwt", 386 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 387 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 388 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 389 | { 390 | sub = validators.required(function(val) return false end) 391 | } 392 | ) 393 | ngx.say(jwt_obj["verified"]) 394 | ngx.say(jwt_obj["reason"]) 395 | '; 396 | } 397 | --- request 398 | GET /t 399 | --- response_body 400 | false 401 | Claim 'sub' ('Test Subject') returned failure 402 | --- no_error_log 403 | [error] 404 | 405 | 406 | === TEST 14: JWT with sub claim and with function that errors with a string only 407 | --- http_config eval: $::HttpConfig 408 | --- config 409 | location /t { 410 | content_by_lua ' 411 | local jwt = require "resty.jwt" 412 | local validators = require "resty.jwt-validators" 413 | local jwt_obj = jwt:verify( 414 | "lua-resty-jwt", 415 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 416 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 417 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 418 | { 419 | sub = validators.required(function(val) error("Error String") end) 420 | } 421 | ) 422 | ngx.say(jwt_obj["verified"]) 423 | ngx.say(jwt_obj["reason"]) 424 | '; 425 | } 426 | --- request 427 | GET /t 428 | --- response_body 429 | false 430 | Error String 431 | --- no_error_log 432 | [error] 433 | 434 | 435 | === TEST 15: JWT with sub claim and with function that does nothing (so it checks existance only) 436 | --- http_config eval: $::HttpConfig 437 | --- config 438 | location /t { 439 | content_by_lua ' 440 | local jwt = require "resty.jwt" 441 | local validators = require "resty.jwt-validators" 442 | local jwt_obj = jwt:verify( 443 | "lua-resty-jwt", 444 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 445 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 446 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 447 | { 448 | sub = validators.required(function(val) end) 449 | } 450 | ) 451 | ngx.say(jwt_obj["verified"]) 452 | ngx.say(jwt_obj["reason"]) 453 | '; 454 | } 455 | --- request 456 | GET /t 457 | --- response_body 458 | true 459 | everything is awesome~ :p 460 | --- no_error_log 461 | [error] 462 | 463 | 464 | === TEST 16: JWT without sub claim and with function that does nothing (so it checks existance only) 465 | --- http_config eval: $::HttpConfig 466 | --- config 467 | location /t { 468 | content_by_lua ' 469 | local jwt = require "resty.jwt" 470 | local validators = require "resty.jwt-validators" 471 | local jwt_obj = jwt:verify( 472 | "lua-resty-jwt", 473 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 474 | ".eyJmb28iOiJiYXIifQ" .. 475 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 476 | { 477 | sub = validators.required(function(val) end) 478 | } 479 | ) 480 | ngx.say(jwt_obj["verified"]) 481 | ngx.say(jwt_obj["reason"]) 482 | '; 483 | } 484 | --- request 485 | GET /t 486 | --- response_body 487 | false 488 | 'sub' claim is required. 489 | --- no_error_log 490 | [error] 491 | 492 | 493 | === TEST 17: JWT verification that does "bad things" to the object 494 | --- http_config eval: $::HttpConfig 495 | --- config 496 | location /t { 497 | content_by_lua ' 498 | local jwt = require "resty.jwt" 499 | local validators = require "resty.jwt-validators" 500 | local cjson = require "cjson.safe" 501 | local jwt_obj = jwt:verify( 502 | "lua-resty-jwt", 503 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 504 | ".eyJmb28iOiJiYXIifQ" .. 505 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 506 | { 507 | sub = function(val, claim, jwt_json) 508 | local tgt_obj = cjson.decode(jwt_json) 509 | ngx.say(tgt_obj.payload["foo"]) 510 | tgt_obj["BAD"] = true 511 | jwt_json = "GO AWAY" 512 | end 513 | } 514 | ) 515 | ngx.say(jwt_obj["verified"]) 516 | ngx.say(jwt_obj["reason"]) 517 | ngx.say(jwt_obj["BAD"]) 518 | '; 519 | } 520 | --- request 521 | GET /t 522 | --- response_body 523 | bar 524 | true 525 | everything is awesome~ :p 526 | nil 527 | --- no_error_log 528 | [error] 529 | 530 | 531 | === TEST 18: JWT with sub claim and full-object validation claim 532 | --- http_config eval: $::HttpConfig 533 | --- config 534 | location /t { 535 | content_by_lua ' 536 | local jwt = require "resty.jwt" 537 | local validators = require "resty.jwt-validators" 538 | local jwt_obj = jwt:verify( 539 | "lua-resty-jwt", 540 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 541 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 542 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 543 | { 544 | __jwt = function(val, claim, jwt_json) 545 | if claim ~= "__jwt" then error("Claim is not __jwt") end 546 | if type(val) ~= "table" then error("Value is not a table") end 547 | ngx.say("Checking " .. val.payload.sub) 548 | return val.payload.sub == "Test Subject" and true or false 549 | end 550 | } 551 | ) 552 | ngx.say(jwt_obj["verified"]) 553 | ngx.say(jwt_obj["reason"]) 554 | '; 555 | } 556 | --- request 557 | GET /t 558 | --- response_body 559 | Checking Test Subject 560 | true 561 | everything is awesome~ :p 562 | --- no_error_log 563 | [error] 564 | 565 | 566 | === TEST 19: JWT full-object verification that does "bad things" to the value 567 | --- http_config eval: $::HttpConfig 568 | --- config 569 | location /t { 570 | content_by_lua ' 571 | local jwt = require "resty.jwt" 572 | local validators = require "resty.jwt-validators" 573 | local jwt_obj = jwt:verify( 574 | "lua-resty-jwt", 575 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 576 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 577 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 578 | { 579 | __jwt = function(val, claim, jwt_json) 580 | ngx.say(val.payload["foo"]) 581 | val.payload["BAD"] = true 582 | val["HELLO"] = "THERE" 583 | return val.payload.sub == "Test Subject" and true or false 584 | end 585 | }, 586 | { 587 | __jwt = function(val, claim, jwt_json) 588 | ngx.say(val.payload["foo"]) 589 | if val.payload["BAD"] then error("You have been poisoned!") end 590 | if val["HELLO"] then error ("HELLO " .. val["HELLO"]) end 591 | end 592 | } 593 | ) 594 | ngx.say(jwt_obj["verified"]) 595 | ngx.say(jwt_obj["reason"]) 596 | ngx.say(jwt_obj.payload["BAD"]) 597 | ngx.say(jwt_obj["HELLO"]) 598 | '; 599 | } 600 | --- request 601 | GET /t 602 | --- response_body 603 | bar 604 | bar 605 | true 606 | everything is awesome~ :p 607 | nil 608 | nil 609 | --- no_error_log 610 | [error] 611 | 612 | 613 | === TEST 20: Multiple claim specs 614 | --- http_config eval: $::HttpConfig 615 | --- config 616 | location /t { 617 | content_by_lua ' 618 | local jwt = require "resty.jwt" 619 | local validators = require "resty.jwt-validators" 620 | local cjson = require "cjson.safe" 621 | local jwt_obj = jwt:verify( 622 | "lua-resty-jwt", 623 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 624 | ".eyJmb28iOiJiYXIiLCJzdWIiOiJUZXN0IFN1YmplY3QifQ" .. 625 | ".UDSQ6edgmmSR9Us53p7Mg2MvcsbVNLCQISJj-rE7zPI", 626 | { 627 | __jwt = function(val, claim, jwt_json) 628 | ngx.say("BEFORE") 629 | end 630 | }, 631 | { 632 | __jwt = function(val, claim, jwt_json) 633 | ngx.say("DURING") 634 | end, 635 | sub = validators.equals("Test Subject") 636 | }, 637 | { 638 | __jwt = function(val, claim, jwt_json) 639 | ngx.say("AFTER") 640 | end 641 | } 642 | ) 643 | ngx.say(jwt_obj["verified"]) 644 | ngx.say(jwt_obj["reason"]) 645 | '; 646 | } 647 | --- request 648 | GET /t 649 | --- response_body 650 | BEFORE 651 | DURING 652 | AFTER 653 | true 654 | everything is awesome~ :p 655 | --- no_error_log 656 | [error] 657 | 658 | 659 | === TEST 21: JWT validate exp by default 660 | --- http_config eval: $::HttpConfig 661 | --- config 662 | location /t { 663 | content_by_lua ' 664 | local jwt = require "resty.jwt" 665 | local jwt_obj = jwt:verify( 666 | "lua-resty-jwt", 667 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 668 | ".eyJmb28iOiJiYXIiLCJleHAiOjB9" .. 669 | ".btivkb1guN1sQBYYVcrigEuNVvDOp1PDrbgaNSD3Whg" 670 | ) 671 | ngx.say(jwt_obj["verified"]) 672 | ngx.say(jwt_obj["reason"]) 673 | '; 674 | } 675 | --- request 676 | GET /t 677 | --- response_body 678 | false 679 | 'exp' claim expired at Thu, 01 Jan 1970 00:00:00 GMT 680 | --- no_error_log 681 | [error] 682 | 683 | 684 | -------------------------------------------------------------------------------- /t/verify.claims.issuer.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(2); 4 | 5 | plan tests => repeat_each() * (3 * blocks()); 6 | 7 | our $HttpConfig = <<'_EOC_'; 8 | lua_package_path 'lib/?.lua;;'; 9 | _EOC_ 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | === TEST 1: JWT without iss claim without valid_issuers requirement 18 | --- http_config eval: $::HttpConfig 19 | --- config 20 | location /t { 21 | content_by_lua ' 22 | local jwt = require "resty.jwt" 23 | local jwt_obj = jwt:verify( 24 | "lua-resty-jwt", 25 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 26 | ".eyJmb28iOiJiYXIifQ" .. 27 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 28 | { } 29 | ) 30 | ngx.say(jwt_obj["verified"]) 31 | ngx.say(jwt_obj["reason"]) 32 | '; 33 | } 34 | --- request 35 | GET /t 36 | --- response_body 37 | true 38 | everything is awesome~ :p 39 | --- no_error_log 40 | [error] 41 | 42 | 43 | === TEST 2: JWT without iss claim with malformed valid_issuers requirement 44 | --- http_config eval: $::HttpConfig 45 | --- config 46 | location /t { 47 | content_by_lua ' 48 | local jwt = require "resty.jwt" 49 | local jwt_obj = jwt:verify( 50 | "lua-resty-jwt", 51 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 52 | ".eyJmb28iOiJiYXIifQ" .. 53 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 54 | { valid_issuers = 17 } 55 | ) 56 | ngx.say(jwt_obj["verified"]) 57 | ngx.say(jwt_obj["reason"]) 58 | '; 59 | } 60 | --- request 61 | GET /t 62 | --- error_code: 500 63 | --- error_log 64 | Cannot create validator for non-table check_values 65 | [error] 66 | 67 | 68 | === TEST 3: JWT without iss claim with malformed valid_issuers requirement - Take 2 69 | --- http_config eval: $::HttpConfig 70 | --- config 71 | location /t { 72 | content_by_lua ' 73 | local jwt = require "resty.jwt" 74 | local jwt_obj = jwt:verify( 75 | "lua-resty-jwt", 76 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 77 | ".eyJmb28iOiJiYXIifQ" .. 78 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 79 | { valid_issuers = { } } 80 | ) 81 | ngx.say(jwt_obj["verified"]) 82 | ngx.say(jwt_obj["reason"]) 83 | '; 84 | } 85 | --- request 86 | GET /t 87 | --- error_code: 500 88 | --- error_log 89 | Cannot create validator for empty table check_values 90 | [error] 91 | 92 | 93 | === TEST 4: JWT without iss claim with malformed valid_issuers requirement - Take 3 94 | --- http_config eval: $::HttpConfig 95 | --- config 96 | location /t { 97 | content_by_lua ' 98 | local jwt = require "resty.jwt" 99 | local jwt_obj = jwt:verify( 100 | "lua-resty-jwt", 101 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 102 | ".eyJmb28iOiJiYXIifQ" .. 103 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 104 | { valid_issuers = { "a", "b", true } } 105 | ) 106 | ngx.say(jwt_obj["verified"]) 107 | ngx.say(jwt_obj["reason"]) 108 | '; 109 | } 110 | --- request 111 | GET /t 112 | --- error_code: 500 113 | --- error_log 114 | Cannot create validator for non-string table check_values 115 | [error] 116 | 117 | 118 | === TEST 5: JWT without iss claim while valid_issuers specified 119 | --- http_config eval: $::HttpConfig 120 | --- config 121 | location /t { 122 | content_by_lua ' 123 | local jwt = require "resty.jwt" 124 | local jwt_obj = jwt:verify( 125 | "lua-resty-jwt", 126 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 127 | ".eyJmb28iOiJiYXIifQ" .. 128 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 129 | { valid_issuers = { "a", "b" } } 130 | ) 131 | ngx.say(jwt_obj["verified"]) 132 | ngx.say(jwt_obj["reason"]) 133 | '; 134 | } 135 | --- request 136 | GET /t 137 | --- response_body 138 | false 139 | 'iss' claim is required. 140 | --- no_error_log 141 | [error] 142 | 143 | 144 | === TEST 6: JWT with malformed iss claim ("iss": 17) while valid_issuers specified 145 | --- http_config eval: $::HttpConfig 146 | --- config 147 | location /t { 148 | content_by_lua ' 149 | local jwt = require "resty.jwt" 150 | local jwt_obj = jwt:verify( 151 | "lua-resty-jwt", 152 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 153 | ".eyJmb28iOiJiYXIiLCJpc3MiOjE3fQ" .. 154 | ".IYbJt_WGO_2pIM0Mh19HbP5W0y1i9CGw4PNQqjHeIx0", 155 | { valid_issuers = { "a", "b" } } 156 | ) 157 | ngx.say(jwt_obj["verified"]) 158 | ngx.say(jwt_obj["reason"]) 159 | '; 160 | } 161 | --- request 162 | GET /t 163 | --- response_body 164 | false 165 | 'iss' is malformed. Expected to be a string. 166 | --- no_error_log 167 | [error] 168 | 169 | 170 | === TEST 7: JWT with valid but unknown iss claim ("iss": "hello") while valid_issuers specified 171 | --- http_config eval: $::HttpConfig 172 | --- config 173 | location /t { 174 | content_by_lua ' 175 | local jwt = require "resty.jwt" 176 | local jwt_obj = jwt:verify( 177 | "lua-resty-jwt", 178 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 179 | ".eyJmb28iOiJiYXIiLCJpc3MiOiJoZWxsbyJ9" .. 180 | ".d8P9QJIJG2LSgQrLOfADw7WqGugRSD3xl-nmZ0FpmC8", 181 | { valid_issuers = { "a", "b" } } 182 | ) 183 | ngx.say(jwt_obj["verified"]) 184 | ngx.say(jwt_obj["reason"]) 185 | '; 186 | } 187 | --- request 188 | GET /t 189 | --- response_body 190 | false 191 | Claim 'iss' ('hello') returned failure 192 | --- no_error_log 193 | [error] 194 | 195 | 196 | === TEST 8: JWT with valid iss claim ("iss": "hello") while valid_issuers specified 197 | --- http_config eval: $::HttpConfig 198 | --- config 199 | location /t { 200 | content_by_lua ' 201 | local jwt = require "resty.jwt" 202 | local jwt_obj = jwt:verify( 203 | "lua-resty-jwt", 204 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 205 | ".eyJmb28iOiJiYXIiLCJpc3MiOiJoZWxsbyJ9" .. 206 | ".d8P9QJIJG2LSgQrLOfADw7WqGugRSD3xl-nmZ0FpmC8", 207 | { valid_issuers = { "hello" } } 208 | ) 209 | ngx.say(jwt_obj["verified"]) 210 | ngx.say(jwt_obj["reason"]) 211 | '; 212 | } 213 | --- request 214 | GET /t 215 | --- response_body 216 | true 217 | everything is awesome~ :p 218 | --- no_error_log 219 | [error] 220 | 221 | 222 | === TEST 9: JWT with valid iss claim ("iss": "hello") while valid_issuers specified 223 | --- http_config eval: $::HttpConfig 224 | --- config 225 | location /t { 226 | content_by_lua ' 227 | local jwt = require "resty.jwt" 228 | local jwt_obj = jwt:verify( 229 | "lua-resty-jwt", 230 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 231 | ".eyJmb28iOiJiYXIiLCJpc3MiOiJoZWxsbyJ9" .. 232 | ".d8P9QJIJG2LSgQrLOfADw7WqGugRSD3xl-nmZ0FpmC8", 233 | { valid_issuers = { "hello", "a" } } 234 | ) 235 | ngx.say(jwt_obj["verified"]) 236 | ngx.say(jwt_obj["reason"]) 237 | '; 238 | } 239 | --- request 240 | GET /t 241 | --- response_body 242 | true 243 | everything is awesome~ :p 244 | --- no_error_log 245 | [error] 246 | 247 | 248 | === TEST 10: JWT with valid iss claim ("iss": "hello") while valid_issuers specified 249 | --- http_config eval: $::HttpConfig 250 | --- config 251 | location /t { 252 | content_by_lua ' 253 | local jwt = require "resty.jwt" 254 | local jwt_obj = jwt:verify( 255 | "lua-resty-jwt", 256 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 257 | ".eyJmb28iOiJiYXIiLCJpc3MiOiJoZWxsbyJ9" .. 258 | ".d8P9QJIJG2LSgQrLOfADw7WqGugRSD3xl-nmZ0FpmC8", 259 | { valid_issuers = { "a", "hello" } } 260 | ) 261 | ngx.say(jwt_obj["verified"]) 262 | ngx.say(jwt_obj["reason"]) 263 | '; 264 | } 265 | --- request 266 | GET /t 267 | --- response_body 268 | true 269 | everything is awesome~ :p 270 | --- no_error_log 271 | [error] 272 | -------------------------------------------------------------------------------- /t/verify.claims.lifecycle.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(2); 4 | 5 | plan tests => repeat_each() * (3 * blocks()); 6 | 7 | our $HttpConfig = <<'_EOC_'; 8 | lua_package_path 'lib/?.lua;;'; 9 | _EOC_ 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | 18 | === TEST 1: JWT with invalid exp ("exp": "17") 19 | --- http_config eval: $::HttpConfig 20 | --- config 21 | location /t { 22 | content_by_lua ' 23 | local jwt = require "resty.jwt" 24 | local jwt_obj = jwt:verify( 25 | "lua-resty-jwt", 26 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 27 | ".eyJmb28iOiJiYXIiLCJleHAiOiIxNyJ9" .. 28 | ".6gWBliIuNT1qF_RhD1ymI-zRyN38zGme0dHvYkOFgxM", 29 | { lifetime_grace_period = 0 } 30 | ) 31 | ngx.say(jwt_obj["verified"]) 32 | ngx.say(jwt_obj["reason"]) 33 | '; 34 | } 35 | --- request 36 | GET /t 37 | --- response_body 38 | false 39 | 'exp' is malformed. Expected to be a positive numeric value. 40 | --- no_error_log 41 | [error] 42 | 43 | 44 | === TEST 2: JWT with invalid exp ("exp": -17) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | content_by_lua ' 49 | local jwt = require "resty.jwt" 50 | local jwt_obj = jwt:verify( 51 | "lua-resty-jwt", 52 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 53 | ".eyJmb28iOiJiYXIiLCJleHAiOi0xN30" .. 54 | ".Jd3_eeMBJeWAeyke5SbXD3TecVPpci7lNLWGze9OP9o", 55 | { lifetime_grace_period = 0 } 56 | ) 57 | ngx.say(jwt_obj["verified"]) 58 | ngx.say(jwt_obj["reason"]) 59 | '; 60 | } 61 | --- request 62 | GET /t 63 | --- response_body 64 | false 65 | 'exp' is malformed. Expected to be a positive numeric value. 66 | --- no_error_log 67 | [error] 68 | 69 | 70 | === TEST 3: JWT with invalid nbf ("nbf": "17") 71 | --- http_config eval: $::HttpConfig 72 | --- config 73 | location /t { 74 | content_by_lua ' 75 | local jwt = require "resty.jwt" 76 | local jwt_obj = jwt:verify( 77 | "lua-resty-jwt", 78 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 79 | ".eyJmb28iOiJiYXIiLCJuYmYiOiIxNyJ9" .. 80 | ".kYzPvYDRiW37rsdYNfFd57KDBuZpm1loCRIJSUlQjbE", 81 | { lifetime_grace_period = 0 } 82 | ) 83 | ngx.say(jwt_obj["verified"]) 84 | ngx.say(jwt_obj["reason"]) 85 | '; 86 | } 87 | --- request 88 | GET /t 89 | --- response_body 90 | false 91 | 'nbf' is malformed. Expected to be a positive numeric value. 92 | --- no_error_log 93 | [error] 94 | 95 | 96 | === TEST 4: JWT with invalid nbf ("nbf": -17) 97 | --- http_config eval: $::HttpConfig 98 | --- config 99 | location /t { 100 | content_by_lua ' 101 | local jwt = require "resty.jwt" 102 | local jwt_obj = jwt:verify( 103 | "lua-resty-jwt", 104 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 105 | ".eyJmb28iOiJiYXIiLCJuYmYiOi0xN30" .. 106 | ".jNUyAIYISmDcemGO3gE17byPZ_ZO-WZxaMt59UNslPc", 107 | { lifetime_grace_period = 0 } 108 | ) 109 | ngx.say(jwt_obj["verified"]) 110 | ngx.say(jwt_obj["reason"]) 111 | '; 112 | } 113 | --- request 114 | GET /t 115 | --- response_body 116 | false 117 | 'nbf' is malformed. Expected to be a positive numeric value. 118 | --- no_error_log 119 | [error] 120 | 121 | 122 | === TEST 5: JWT with invalid negative lifetime grace period 123 | --- http_config eval: $::HttpConfig 124 | --- config 125 | location /t { 126 | content_by_lua ' 127 | local jwt = require "resty.jwt" 128 | local jwt_obj = jwt:verify( 129 | "lua-resty-jwt", 130 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 131 | ".eyJmb28iOiJiYXIifQ" .. 132 | ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY", 133 | { lifetime_grace_period = -1 } 134 | ) 135 | ngx.say(jwt_obj["verified"]) 136 | ngx.say(jwt_obj["reason"]) 137 | '; 138 | } 139 | --- request 140 | GET /t 141 | --- error_code: 500 142 | --- error_log 143 | leeway must be a non-negative number 144 | [error] 145 | 146 | 147 | === TEST 6: JWT with invalid alpha lifetime grace period 148 | --- http_config eval: $::HttpConfig 149 | --- config 150 | location /t { 151 | content_by_lua ' 152 | local jwt = require "resty.jwt" 153 | local jwt_obj = jwt:verify( 154 | "lua-resty-jwt", 155 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" .. 156 | ".eyJmb28iOiJiYXIifQ" .. 157 | ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY", 158 | { lifetime_grace_period = "boom ?" } 159 | ) 160 | ngx.say(jwt_obj["verified"]) 161 | ngx.say(jwt_obj["reason"]) 162 | '; 163 | } 164 | --- request 165 | GET /t 166 | --- error_code: 500 167 | --- error_log 168 | leeway must be a non-negative number 169 | [error] 170 | 171 | 172 | === TEST 7: JWT with no lifetime grace period and valid exp ("exp": 9999999999) 173 | --- http_config eval: $::HttpConfig 174 | --- config 175 | location /t { 176 | content_by_lua ' 177 | local jwt = require "resty.jwt" 178 | local jwt_obj = jwt:verify( 179 | "lua-resty-jwt", 180 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 181 | ".eyJmb28iOiJiYXIiLCJleHAiOjk5OTk5OTk5OTl9" .. 182 | ".Y503HYultweqOpvvNF3fj2FTb_rH7ZwKAXap6cPqXjw", 183 | { lifetime_grace_period = 0 } 184 | ) 185 | ngx.say(jwt_obj["verified"]) 186 | ngx.say(jwt_obj["reason"]) 187 | '; 188 | } 189 | --- request 190 | GET /t 191 | --- response_body 192 | true 193 | everything is awesome~ :p 194 | --- no_error_log 195 | [error] 196 | 197 | 198 | === TEST 8: JWT with no lifetime grace period and invalid exp ("exp": 0) 199 | --- http_config eval: $::HttpConfig 200 | --- config 201 | location /t { 202 | content_by_lua ' 203 | local jwt = require "resty.jwt" 204 | local jwt_obj = jwt:verify( 205 | "lua-resty-jwt", 206 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 207 | ".eyJmb28iOiJiYXIiLCJleHAiOjB9" .. 208 | ".btivkb1guN1sQBYYVcrigEuNVvDOp1PDrbgaNSD3Whg", 209 | { lifetime_grace_period = 0 } 210 | ) 211 | ngx.say(jwt_obj["verified"]) 212 | ngx.say(jwt_obj["reason"]) 213 | '; 214 | } 215 | --- request 216 | GET /t 217 | --- response_body 218 | false 219 | 'exp' claim expired at Thu, 01 Jan 1970 00:00:00 GMT 220 | --- no_error_log 221 | [error] 222 | 223 | 224 | === TEST 9: JWT with no lifetime grace period and valid nbf ("nbf": 0) 225 | --- http_config eval: $::HttpConfig 226 | --- config 227 | location /t { 228 | content_by_lua ' 229 | local jwt = require "resty.jwt" 230 | local jwt_obj = jwt:verify( 231 | "lua-resty-jwt", 232 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 233 | ".eyJmb28iOiJiYXIiLCJuYmYiOjB9" .. 234 | ".qZeWRQBHZhRcszwbiL7JV6Nf-irT75u4IHhoQBTqkzo", 235 | { lifetime_grace_period = 0 } 236 | ) 237 | ngx.say(jwt_obj["verified"]) 238 | ngx.say(jwt_obj["reason"]) 239 | '; 240 | } 241 | --- request 242 | GET /t 243 | --- response_body 244 | true 245 | everything is awesome~ :p 246 | --- no_error_log 247 | [error] 248 | 249 | 250 | === TEST 10: JWT with no lifetime grace period and invalid nbf ("nbf": 9999999999) 251 | --- http_config eval: $::HttpConfig 252 | --- config 253 | location /t { 254 | content_by_lua ' 255 | local jwt = require "resty.jwt" 256 | local jwt_obj = jwt:verify( 257 | "lua-resty-jwt", 258 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 259 | ".eyJmb28iOiJiYXIiLCJuYmYiOjk5OTk5OTk5OTl9" .. 260 | ".Wfu3owxbzlrb0GXvV0D22Si8WEDP0WeRGwZNPAoYHMI", 261 | { lifetime_grace_period = 0 } 262 | ) 263 | ngx.say(jwt_obj["verified"]) 264 | ngx.say(jwt_obj["reason"]) 265 | '; 266 | } 267 | --- request 268 | GET /t 269 | --- response_body 270 | false 271 | 'nbf' claim not valid until Sat, 20 Nov 2286 17:46:39 GMT 272 | --- no_error_log 273 | [error] 274 | 275 | 276 | === TEST 11: JWT with super large lifetime grace period and invalid nbf ("nbf": 9999999999) 277 | --- http_config eval: $::HttpConfig 278 | --- config 279 | location /t { 280 | content_by_lua ' 281 | local jwt = require "resty.jwt" 282 | local jwt_obj = jwt:verify( 283 | "lua-resty-jwt", 284 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 285 | ".eyJmb28iOiJiYXIiLCJuYmYiOjk5OTk5OTk5OTl9" .. 286 | ".Wfu3owxbzlrb0GXvV0D22Si8WEDP0WeRGwZNPAoYHMI", 287 | { lifetime_grace_period = 9999999999 } 288 | ) 289 | ngx.say(jwt_obj["verified"]) 290 | ngx.say(jwt_obj["reason"]) 291 | '; 292 | } 293 | --- request 294 | GET /t 295 | --- response_body 296 | true 297 | everything is awesome~ :p 298 | --- no_error_log 299 | [error] 300 | 301 | 302 | === TEST 12: JWT with super large lifetime grace period and invalid exp ("exp": 0) 303 | --- http_config eval: $::HttpConfig 304 | --- config 305 | location /t { 306 | content_by_lua ' 307 | local jwt = require "resty.jwt" 308 | local jwt_obj = jwt:verify( 309 | "lua-resty-jwt", 310 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 311 | ".eyJmb28iOiJiYXIiLCJleHAiOjB9" .. 312 | ".btivkb1guN1sQBYYVcrigEuNVvDOp1PDrbgaNSD3Whg", 313 | { lifetime_grace_period = 9999999999 } 314 | ) 315 | ngx.say(jwt_obj["verified"]) 316 | ngx.say(jwt_obj["reason"]) 317 | '; 318 | } 319 | --- request 320 | GET /t 321 | --- response_body 322 | true 323 | everything is awesome~ :p 324 | --- no_error_log 325 | [error] 326 | 327 | 328 | === TEST 13: JWT without exp nor nbf claim without lifetime related requirement 329 | --- http_config eval: $::HttpConfig 330 | --- config 331 | location /t { 332 | content_by_lua ' 333 | local jwt = require "resty.jwt" 334 | local jwt_obj = jwt:verify( 335 | "lua-resty-jwt", 336 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 337 | ".eyJmb28iOiJiYXIifQ" .. 338 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 339 | { } 340 | ) 341 | ngx.say(jwt_obj["verified"]) 342 | ngx.say(jwt_obj["reason"]) 343 | '; 344 | } 345 | --- request 346 | GET /t 347 | --- response_body 348 | true 349 | everything is awesome~ :p 350 | --- no_error_log 351 | [error] 352 | 353 | 354 | === TEST 14: JWT without exp nor nbf claim without lifetime related requirement - Take 2 355 | --- http_config eval: $::HttpConfig 356 | --- config 357 | location /t { 358 | content_by_lua ' 359 | local jwt = require "resty.jwt" 360 | local jwt_obj = jwt:verify( 361 | "lua-resty-jwt", 362 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 363 | ".eyJmb28iOiJiYXIifQ" .. 364 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 365 | { require_nbf_claim = false } 366 | ) 367 | ngx.say(jwt_obj["verified"]) 368 | ngx.say(jwt_obj["reason"]) 369 | '; 370 | } 371 | --- request 372 | GET /t 373 | --- response_body 374 | true 375 | everything is awesome~ :p 376 | --- no_error_log 377 | [error] 378 | 379 | 380 | === TEST 15: JWT without exp nor nbf claim without lifetime related requirement - Take 3 381 | --- http_config eval: $::HttpConfig 382 | --- config 383 | location /t { 384 | content_by_lua ' 385 | local jwt = require "resty.jwt" 386 | local jwt_obj = jwt:verify( 387 | "lua-resty-jwt", 388 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 389 | ".eyJmb28iOiJiYXIifQ" .. 390 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 391 | { require_exp_claim = false } 392 | ) 393 | ngx.say(jwt_obj["verified"]) 394 | ngx.say(jwt_obj["reason"]) 395 | '; 396 | } 397 | --- request 398 | GET /t 399 | --- response_body 400 | true 401 | everything is awesome~ :p 402 | --- no_error_log 403 | [error] 404 | 405 | 406 | === TEST 16: JWT without exp nor nbf claim while lifetime grace period specified 407 | --- http_config eval: $::HttpConfig 408 | --- config 409 | location /t { 410 | content_by_lua ' 411 | local jwt = require "resty.jwt" 412 | local jwt_obj = jwt:verify( 413 | "lua-resty-jwt", 414 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 415 | ".eyJmb28iOiJiYXIifQ" .. 416 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 417 | { lifetime_grace_period = 1 } 418 | ) 419 | ngx.say(jwt_obj["verified"]) 420 | ngx.say(jwt_obj["reason"]) 421 | '; 422 | } 423 | --- request 424 | GET /t 425 | --- response_body 426 | false 427 | Missing one of claims - [ nbf, exp ]. 428 | --- no_error_log 429 | [error] 430 | 431 | 432 | === TEST 17: JWT without exp nor nbf claim while lifetime grace period specified - Take 2 433 | --- http_config eval: $::HttpConfig 434 | --- config 435 | location /t { 436 | content_by_lua ' 437 | local jwt = require "resty.jwt" 438 | local jwt_obj = jwt:verify( 439 | "lua-resty-jwt", 440 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 441 | ".eyJmb28iOiJiYXIifQ" .. 442 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 443 | { lifetime_grace_period = 0 } 444 | ) 445 | ngx.say(jwt_obj["verified"]) 446 | ngx.say(jwt_obj["reason"]) 447 | '; 448 | } 449 | --- request 450 | GET /t 451 | --- response_body 452 | false 453 | Missing one of claims - [ nbf, exp ]. 454 | --- no_error_log 455 | [error] 456 | 457 | 458 | === TEST 18: JWT without exp nor nbf claim while exp claim required 459 | --- http_config eval: $::HttpConfig 460 | --- config 461 | location /t { 462 | content_by_lua ' 463 | local jwt = require "resty.jwt" 464 | local jwt_obj = jwt:verify( 465 | "lua-resty-jwt", 466 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 467 | ".eyJmb28iOiJiYXIifQ" .. 468 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 469 | { require_exp_claim = true } 470 | ) 471 | ngx.say(jwt_obj["verified"]) 472 | ngx.say(jwt_obj["reason"]) 473 | '; 474 | } 475 | --- request 476 | GET /t 477 | --- response_body 478 | false 479 | 'exp' claim is required. 480 | --- no_error_log 481 | [error] 482 | 483 | 484 | === TEST 19: JWT without exp nor nbf claim while nbf claim required 485 | --- http_config eval: $::HttpConfig 486 | --- config 487 | location /t { 488 | content_by_lua ' 489 | local jwt = require "resty.jwt" 490 | local jwt_obj = jwt:verify( 491 | "lua-resty-jwt", 492 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 493 | ".eyJmb28iOiJiYXIifQ" .. 494 | ".VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY", 495 | { require_nbf_claim = true } 496 | ) 497 | ngx.say(jwt_obj["verified"]) 498 | ngx.say(jwt_obj["reason"]) 499 | '; 500 | } 501 | --- request 502 | GET /t 503 | --- response_body 504 | false 505 | 'nbf' claim is required. 506 | --- no_error_log 507 | [error] 508 | 509 | 510 | === TEST 20: JWT with valid exp ("exp": 9999999999) while exp claim required 511 | --- http_config eval: $::HttpConfig 512 | --- config 513 | location /t { 514 | content_by_lua ' 515 | local jwt = require "resty.jwt" 516 | local jwt_obj = jwt:verify( 517 | "lua-resty-jwt", 518 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 519 | ".eyJmb28iOiJiYXIiLCJleHAiOjk5OTk5OTk5OTl9" .. 520 | ".Y503HYultweqOpvvNF3fj2FTb_rH7ZwKAXap6cPqXjw", 521 | { require_exp_claim = true } 522 | ) 523 | ngx.say(jwt_obj["verified"]) 524 | ngx.say(jwt_obj["reason"]) 525 | '; 526 | } 527 | --- request 528 | GET /t 529 | --- response_body 530 | true 531 | everything is awesome~ :p 532 | --- no_error_log 533 | [error] 534 | 535 | 536 | === TEST 21: JWT with valid exp ("exp": 9999999999) while nbf claim required 537 | --- http_config eval: $::HttpConfig 538 | --- config 539 | location /t { 540 | content_by_lua ' 541 | local jwt = require "resty.jwt" 542 | local jwt_obj = jwt:verify( 543 | "lua-resty-jwt", 544 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 545 | ".eyJmb28iOiJiYXIiLCJleHAiOjk5OTk5OTk5OTl9" .. 546 | ".Y503HYultweqOpvvNF3fj2FTb_rH7ZwKAXap6cPqXjw", 547 | { require_nbf_claim = true } 548 | ) 549 | ngx.say(jwt_obj["verified"]) 550 | ngx.say(jwt_obj["reason"]) 551 | '; 552 | } 553 | --- request 554 | GET /t 555 | --- response_body 556 | false 557 | 'nbf' claim is required. 558 | --- no_error_log 559 | [error] 560 | 561 | 562 | === TEST 22: JWT with valid nbf ("nbf": 0) while nbf claim required 563 | --- http_config eval: $::HttpConfig 564 | --- config 565 | location /t { 566 | content_by_lua ' 567 | local jwt = require "resty.jwt" 568 | local jwt_obj = jwt:verify( 569 | "lua-resty-jwt", 570 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 571 | ".eyJmb28iOiJiYXIiLCJuYmYiOjB9" .. 572 | ".qZeWRQBHZhRcszwbiL7JV6Nf-irT75u4IHhoQBTqkzo", 573 | { require_nbf_claim = true } 574 | ) 575 | ngx.say(jwt_obj["verified"]) 576 | ngx.say(jwt_obj["reason"]) 577 | '; 578 | } 579 | --- request 580 | GET /t 581 | --- response_body 582 | true 583 | everything is awesome~ :p 584 | --- no_error_log 585 | [error] 586 | 587 | 588 | === TEST 23: JWT with valid nbf ("nbf": 0) while exp claim required 589 | --- http_config eval: $::HttpConfig 590 | --- config 591 | location /t { 592 | content_by_lua ' 593 | local jwt = require "resty.jwt" 594 | local jwt_obj = jwt:verify( 595 | "lua-resty-jwt", 596 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" .. 597 | ".eyJmb28iOiJiYXIiLCJuYmYiOjB9" .. 598 | ".qZeWRQBHZhRcszwbiL7JV6Nf-irT75u4IHhoQBTqkzo", 599 | { require_exp_claim = true } 600 | ) 601 | ngx.say(jwt_obj["verified"]) 602 | ngx.say(jwt_obj["reason"]) 603 | '; 604 | } 605 | --- request 606 | GET /t 607 | --- response_body 608 | false 609 | 'exp' claim is required. 610 | --- no_error_log 611 | [error] 612 | -------------------------------------------------------------------------------- /testcerts/README.md: -------------------------------------------------------------------------------- 1 | # Test certificates 2 | 3 | Those certs have been retrieved from http://fm4dd.com/openssl/certexamples.htm 4 | 5 | - Root CA cert: http://fm4dd.com/openssl/source/PEM/certs/frank4dd-cacert.pem 6 | - 2048b RSA cert: http://fm4dd.com/openssl/source/PEM/certs/2048b-rsa-example-cert.pem 7 | - 2048 RSA keypair: http://fm4dd.com/openssl/source/PEM/keys/2048b-rsa-example-keypair.pem 8 | -------------------------------------------------------------------------------- /testcerts/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC2jCCAkMCAg38MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG 3 | A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE 4 | MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl 5 | YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw 6 | ODIyMDUyNzQxWhcNMTcwODIxMDUyNzQxWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE 7 | CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs 8 | ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0z9FeMynsC8+u 9 | dvX+LciZxnh5uRj4C9S6tNeeAlIGCfQYk0zUcNFCoCkTknNQd/YEiawDLNbxBqut 10 | bMDZ1aarys1a0lYmUeVLCIqvzBkPJTSQsCopQQ9V8WuT252zzNzs68dVGNdCJd5J 11 | NRQykpwexmnjPPv0mvj7i8XgG379TyW6P+WWV5okeUkXJ9eJS2ouDYdR2SM9BoVW 12 | +FgxDu6BmXhozW5EfsnajFp7HL8kQClI0QOc79yuKl3492rH6bzFsFn2lfwWy9ic 13 | 7cP8EpCTeFp1tFaD+vxBhPZkeTQ1HKx6hQ5zeHIB5ySJJZ7af2W8r4eTGYzbdRW2 14 | 4DDHCPhZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQMv+BFvGdMVzkQaQ3/+2noVz 15 | /uAKbzpEL8xTcxYyP3lkOeh4FoxiSWqy5pGFALdPONoDuYFpLhjJSZaEwuvjI/Tr 16 | rGhLV1pRG9frwDFshqD2Vaj4ENBCBh6UpeBop5+285zQ4SI7q4U9oSebUDJiuOx6 17 | +tZ9KynmrbJpTSi0+BM= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /testcerts/privatekey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAtM/RXjMp7AvPrnb1/i3ImcZ4ebkY+AvUurTXngJSBgn0GJNM 3 | 1HDRQqApE5JzUHf2BImsAyzW8QarrWzA2dWmq8rNWtJWJlHlSwiKr8wZDyU0kLAq 4 | KUEPVfFrk9uds8zc7OvHVRjXQiXeSTUUMpKcHsZp4zz79Jr4+4vF4Bt+/U8luj/l 5 | lleaJHlJFyfXiUtqLg2HUdkjPQaFVvhYMQ7ugZl4aM1uRH7J2oxaexy/JEApSNED 6 | nO/cripd+Pdqx+m8xbBZ9pX8FsvYnO3D/BKQk3hadbRWg/r8QYT2ZHk0NRyseoUO 7 | c3hyAeckiSWe2n9lvK+HkxmM23UVtuAwxwj4WQIDAQABAoIBAE76H0d4La2PEy3v 8 | hE98DA0vJdx1PzTJZigPacb42H8OxfIeFQcOKDlj381OwNO7MliVEe9pHJG3CjH8 9 | ONhtfBm5wa0UBtFCIFd/6aQUEDYPWECC0kemxV4Sz5yL5vxsVWufKThAW3XnOIrd 10 | hm74nvzKSeIZ9yvGrU6ipNHY8MUPm0DQVrVYE5MiKjKVExQ4uRAolV2hlmeQDlSt 11 | k85S0TUOWO1EvJZhsVVs7dBjjY10hIjv3gZPAO8CN85JzMeaNbmWv4RQj0B997in 12 | rqlOa5qYYt80tAWO4hmPRKCrv6PgThz8C0Cd8AgwNzvQD2d4JpmxxTzBT6/5lRng 13 | Hhj/wQECgYEA2jxC0a4lGmp1q2aYE1Zyiq0UqjxA92pwFYJg3800MLkf96A+dOhd 14 | wDAc5aAKN8vQV5g33vKi5+pIHWUCskhTS8/PPGrfeqIvtphCj6b7LKosBOhdzrRD 15 | Osr+Az/SiR2h5l2lr/v7I8I86RTY7MBk4QcRb601kSagWLDNVzSSdhECgYEA1Bm0 16 | 0sByqkQmFoUNRjwmShPfJeVLTCr1G4clljl6MqHmGyRDHxtcp1+CXlyJJemLQY2A 17 | qrM7/T4x2ta6ME2WgDydFe9M8oU3BbefNYovS6YnoyBqxCx7yZ1vO0Jo40rZI8Bi 18 | KoCi6e0Hugg4xyPRz9TTNLmr/yEC1qQesMhM9ckCgYEArsT7rfgMdq8zNOSgfTwJ 19 | 1sztc7d1P67ZvCABfLlVRn+6/hAydGVyTus4+RvFkxGB8+RPOhiOJbQVtJSkKCqL 20 | qnbtu7DK7+ba1xvwkiJjnE1bm0KLfXIXNQpDik6eSHiWo2nzuo/Ne8GeDftIDbG2 21 | GBAVAp5v+6I3X0+X4nKTqEECgYEAwT4Cj5mjXxnkEdR7eahHwmpEf0RfzC+/Tate 22 | RXZsrUDwY34wYWEOk7fjEZIBqrcTl1ATEHNojpxh096bmHK4UnHnNRrn4nYY4W6g 23 | 8ajK2oOxzWA1pjJZPiHgO/+PjLafC4G2br7wr2y0A3yGLnmmKVLgc0NPP42WBnVV 24 | OP/ljnECgYABlDdJCAehDNSv4mdEzY5bfD+VBFd2QsgE1hYhmUYYRNlgIfIL9Y8e 25 | CduqXFLNZ/LHdmtYembgUqrMiJTUqcbSrJt26kBQx0az3LAV+J2p68PQ85KR9ZPy 26 | N1jEnRqpAwEdw7S+8l0yVyaNkm66eRI80p+w3AxNbS9hJ/7UlV3lGA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /testcerts/pubkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtM/RXjMp7AvPrnb1/i3I 3 | mcZ4ebkY+AvUurTXngJSBgn0GJNM1HDRQqApE5JzUHf2BImsAyzW8QarrWzA2dWm 4 | q8rNWtJWJlHlSwiKr8wZDyU0kLAqKUEPVfFrk9uds8zc7OvHVRjXQiXeSTUUMpKc 5 | HsZp4zz79Jr4+4vF4Bt+/U8luj/llleaJHlJFyfXiUtqLg2HUdkjPQaFVvhYMQ7u 6 | gZl4aM1uRH7J2oxaexy/JEApSNEDnO/cripd+Pdqx+m8xbBZ9pX8FsvYnO3D/BKQ 7 | k3hadbRWg/r8QYT2ZHk0NRyseoUOc3hyAeckiSWe2n9lvK+HkxmM23UVtuAwxwj4 8 | WQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /testcerts/root.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDvDCCAyWgAwIBAgIJAMbHBAm8IlugMA0GCSqGSIb3DQEBBQUAMIGbMQswCQYD 3 | VQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNV 4 | BAoTCEZyYW5rNEREMRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMT 5 | D0ZyYW5rNEREIFdlYiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRk 6 | ZC5jb20wHhcNMDcxMjA3MTAyMTQ2WhcNMTcxMjA0MTAyMTQ2WjCBmzELMAkGA1UE 7 | BhMCSlAxDjAMBgNVBAgTBVRva3lvMRAwDgYDVQQHEwdDaHVvLWt1MREwDwYDVQQK 8 | EwhGcmFuazRERDEYMBYGA1UECxMPV2ViQ2VydCBTdXBwb3J0MRgwFgYDVQQDEw9G 9 | cmFuazRERCBXZWIgQ0ExIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAZnJhbms0ZGQu 10 | Y29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7r7yPJdXmDL2/+L2iogxQ 11 | rLML+10EwAY9lJRCHGPqSJ8if7teqnXgFr6MAEiCwTcLvk4h1UxLrDXmxooegNg1 12 | zx/OODbcc++SfFCGmflwj/wjLpYRwPgux7/QIgrUqzsj2HtdRFd+WPVD4AOtY9gn 13 | xjNXFpVe1zmgAm/UFLdMewIDAQABo4IBBDCCAQAwHQYDVR0OBBYEFGLze+0G1LHV 14 | nH9I5e/FyRVh/dkRMIHQBgNVHSMEgcgwgcWAFGLze+0G1LHVnH9I5e/FyRVh/dkR 15 | oYGhpIGeMIGbMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEDAOBgNVBAcT 16 | B0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNEREMRgwFgYDVQQLEw9XZWJDZXJ0IFN1 17 | cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdlYiBDQTEjMCEGCSqGSIb3DQEJARYU 18 | c3VwcG9ydEBmcmFuazRkZC5jb22CCQDGxwQJvCJboDAMBgNVHRMEBTADAQH/MA0G 19 | CSqGSIb3DQEBBQUAA4GBALosLpHduFOY30wKS2WQ32RzRgh0ZWNlLXWHkQYmzTHN 20 | okwYLy0wGfIqzD1ovLMjDuPMC3MBmQPg8zhd+BY2sgRhgdEBmYWTiw71eZLLmI/e 21 | dQbu1z6rOXJb8EegubJNkYTcuxsKLijIfJDnK2noqPt03puJEsBxosN14XPEhIEO 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /vendor/README.md: -------------------------------------------------------------------------------- 1 | # Third Party Libraries 2 | 3 | This folder contains important 3rd party libraries that not availab on luarocks. 4 | 5 | - resty/hmac.lua [lua-resty-hmac](https://github.com/jkeys089/lua-resty-hmac) 6 | -------------------------------------------------------------------------------- /vendor/resty/hmac.lua: -------------------------------------------------------------------------------- 1 | 2 | local str_util = require "resty.string" 3 | local ffi = require "ffi" 4 | local ffi_new = ffi.new 5 | local ffi_str = ffi.string 6 | local ffi_gc = ffi.gc 7 | local C = ffi.C 8 | local setmetatable = setmetatable 9 | local error = error 10 | 11 | 12 | local _M = { _VERSION = '0.01' } 13 | 14 | local mt = { __index = _M } 15 | 16 | 17 | ffi.cdef[[ 18 | typedef struct engine_st ENGINE; 19 | typedef struct evp_pkey_ctx_st EVP_PKEY_CTX; 20 | typedef struct env_md_ctx_st EVP_MD_CTX; 21 | typedef struct env_md_st EVP_MD; 22 | 23 | struct env_md_ctx_st 24 | { 25 | const EVP_MD *digest; 26 | ENGINE *engine; 27 | unsigned long flags; 28 | void *md_data; 29 | EVP_PKEY_CTX *pctx; 30 | int (*update)(EVP_MD_CTX *ctx,const void *data,size_t count); 31 | }; 32 | 33 | struct env_md_st 34 | { 35 | int type; 36 | int pkey_type; 37 | int md_size; 38 | unsigned long flags; 39 | int (*init)(EVP_MD_CTX *ctx); 40 | int (*update)(EVP_MD_CTX *ctx,const void *data,size_t count); 41 | int (*final)(EVP_MD_CTX *ctx,unsigned char *md); 42 | int (*copy)(EVP_MD_CTX *to,const EVP_MD_CTX *from); 43 | int (*cleanup)(EVP_MD_CTX *ctx); 44 | 45 | int (*sign)(int type, const unsigned char *m, unsigned int m_length, unsigned char *sigret, unsigned int *siglen, void *key); 46 | int (*verify)(int type, const unsigned char *m, unsigned int m_length, const unsigned char *sigbuf, unsigned int siglen, void *key); 47 | int required_pkey_type[5]; 48 | int block_size; 49 | int ctx_size; 50 | int (*md_ctrl)(EVP_MD_CTX *ctx, int cmd, int p1, void *p2); 51 | }; 52 | 53 | typedef struct hmac_ctx_st 54 | { 55 | const EVP_MD *md; 56 | EVP_MD_CTX md_ctx; 57 | EVP_MD_CTX i_ctx; 58 | EVP_MD_CTX o_ctx; 59 | unsigned int key_length; 60 | unsigned char key[128]; 61 | } HMAC_CTX; 62 | 63 | void HMAC_CTX_init(HMAC_CTX *ctx); 64 | void HMAC_CTX_cleanup(HMAC_CTX *ctx); 65 | 66 | int HMAC_Init_ex(HMAC_CTX *ctx, const void *key, int len,const EVP_MD *md, ENGINE *impl); 67 | int HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, size_t len); 68 | int HMAC_Final(HMAC_CTX *ctx, unsigned char *md, unsigned int *len); 69 | 70 | const EVP_MD *EVP_md5(void); 71 | const EVP_MD *EVP_sha1(void); 72 | const EVP_MD *EVP_sha256(void); 73 | const EVP_MD *EVP_sha512(void); 74 | ]] 75 | 76 | local buf = ffi_new("unsigned char[64]") 77 | local res_len = ffi_new("unsigned int[1]") 78 | local ctx_ptr_type = ffi.typeof("HMAC_CTX[1]") 79 | local hashes = { 80 | MD5 = C.EVP_md5(), 81 | SHA1 = C.EVP_sha1(), 82 | SHA256 = C.EVP_sha256(), 83 | SHA512 = C.EVP_sha512() 84 | } 85 | 86 | 87 | _M.ALGOS = hashes 88 | 89 | 90 | function _M.new(self, key, hash_algo) 91 | local ctx = ffi_new(ctx_ptr_type) 92 | 93 | C.HMAC_CTX_init(ctx) 94 | 95 | local _hash_algo = hash_algo or hashes.md5 96 | 97 | if C.HMAC_Init_ex(ctx, key, #key, _hash_algo, nil) == 0 then 98 | return nil 99 | end 100 | 101 | ffi_gc(ctx, C.HMAC_CTX_cleanup) 102 | 103 | return setmetatable({ _ctx = ctx }, mt) 104 | end 105 | 106 | 107 | function _M.update(self, s) 108 | return C.HMAC_Update(self._ctx, s, #s) == 1 109 | end 110 | 111 | 112 | function _M.final(self, s, hex_output) 113 | 114 | if s ~= nil then 115 | if C.HMAC_Update(self._ctx, s, #s) == 0 then 116 | return nil 117 | end 118 | end 119 | 120 | if C.HMAC_Final(self._ctx, buf, res_len) == 1 then 121 | if hex_output == true then 122 | return str_util.to_hex(ffi_str(buf, res_len[0])) 123 | end 124 | return ffi_str(buf, res_len[0]) 125 | end 126 | 127 | return nil 128 | end 129 | 130 | 131 | function _M.reset(self) 132 | return C.HMAC_Init_ex(self._ctx, nil, 0, nil, nil) == 1 133 | end 134 | 135 | return _M 136 | --------------------------------------------------------------------------------