{"id":15538589,"url":"https://github.com/skylothar/lua-resty-jwt","last_synced_at":"2025-04-04T06:07:21.566Z","repository":{"id":23886947,"uuid":"27266201","full_name":"SkyLothar/lua-resty-jwt","owner":"SkyLothar","description":"JWT For The Great Openresty","archived":false,"fork":false,"pushed_at":"2024-01-03T10:53:56.000Z","size":142,"stargazers_count":512,"open_issues_count":37,"forks_count":177,"subscribers_count":23,"default_branch":"master","last_synced_at":"2024-10-30T04:50:09.430Z","etag":null,"topics":["jwt","lua","lua-resty-jwt","ngx-lua"],"latest_commit_sha":null,"homepage":null,"language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SkyLothar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS.md"}},"created_at":"2014-11-28T12:28:56.000Z","updated_at":"2024-10-22T09:59:08.000Z","dependencies_parsed_at":"2024-01-13T08:51:00.421Z","dependency_job_id":"e88bee6b-3be7-43fc-887a-936cf35d29cd","html_url":"https://github.com/SkyLothar/lua-resty-jwt","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SkyLothar%2Flua-resty-jwt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SkyLothar%2Flua-resty-jwt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SkyLothar%2Flua-resty-jwt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SkyLothar%2Flua-resty-jwt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SkyLothar","download_url":"https://codeload.github.com/SkyLothar/lua-resty-jwt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247128744,"owners_count":20888235,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["jwt","lua","lua-resty-jwt","ngx-lua"],"created_at":"2024-10-02T12:04:59.626Z","updated_at":"2025-04-04T06:07:21.540Z","avatar_url":"https://github.com/SkyLothar.png","language":"Perl","readme":"Name\n====\n\nlua-resty-jwt - [JWT](http://self-issued.info/docs/draft-jones-json-web-token-01.html) for ngx_lua and LuaJIT\n\n[![Build Status](https://img.shields.io/travis/SkyLothar/lua-resty-jwt.svg?style=flat-square)](https://travis-ci.org/SkyLothar/lua-resty-jwt)\n\n\n**Attention :exclamation: the hmac lib used here is [lua-resty-hmac](https://github.com/jkeys089/lua-resty-hmac), not the one in luarocks.**\n\nInstallation\n============\n- opm: `opm get SkyLothar/lua-resty-jwt`\n- luarocks: `luarocks install lua-resty-jwt`\n- Head to [release page](https://github.com/SkyLothar/lua-resty-jwt/releases) and download `tar.gz`\n\nversion\n=======\n\n0.1.10\n\n\nTable of Contents\n=================\n\n* [Name](#name)\n* [Status](#status)\n* [Description](#description)\n* [Synopsis](#synopsis)\n* [Methods](#methods)\n    * [sign](#sign)\n    * [verify](#verify)\n    * [load and verify](#load--verify)\n    * [sign JWE](#sign-jwe)\n* [Verification](#verification)\n    * [JWT Validators](#jwt-validators)\n    * [Legacy/Timeframe options](#legacy-timeframe-options)\n* [Example](#examples)\n* [Installation](#installation)\n* [Testing With Docker](#testing-with-docker)\n* [Authors](AUTHORS.md)\n* [See Also](#see-also)\n\nStatus\n======\n\nThis library is under active development but is considered production ready.\n\nDescription\n===========\n\nThis library requires an nginx build with OpenSSL,\nthe [ngx_lua module](http://wiki.nginx.org/HttpLuaModule),\nthe [LuaJIT 2.0](http://luajit.org/luajit.html),\nthe [lua-resty-hmac](https://github.com/jkeys089/lua-resty-hmac),\nand the [lua-resty-string](https://github.com/openresty/lua-resty-string),\n\n\nSynopsis\n========\n\n```lua\n    # nginx.conf:\n\n    lua_package_path \"/path/to/lua-resty-jwt/lib/?.lua;;\";\n\n    server {\n        default_type text/plain;\n        location = /verify {\n            content_by_lua '\n                local cjson = require \"cjson\"\n                local jwt = require \"resty.jwt\"\n\n                local jwt_token = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\" ..\n                    \".eyJmb28iOiJiYXIifQ\" ..\n                    \".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY\"\n                local jwt_obj = jwt:verify(\"lua-resty-jwt\", jwt_token)\n                ngx.say(cjson.encode(jwt_obj))\n            ';\n        }\n        location = /sign {\n            content_by_lua '\n                local cjson = require \"cjson\"\n                local jwt = require \"resty.jwt\"\n\n                local jwt_token = jwt:sign(\n                    \"lua-resty-jwt\",\n                    {\n                        header={typ=\"JWT\", alg=\"HS256\"},\n                        payload={foo=\"bar\"}\n                    }\n                )\n                ngx.say(jwt_token)\n            ';\n        }\n    }\n```\n\n[Back to TOC](#table-of-contents)\n\nMethods\n=======\n\nTo load this library,\n\n1. 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;;\";`.\n2. you use `require` to load the library into a local Lua variable:\n\n```lua\n    local jwt = require \"resty.jwt\"\n```\n\n[Back to TOC](#table-of-contents)\n\n\nsign\n----\n\n`syntax: local jwt_token = jwt:sign(key, table_of_jwt)`\n\nsign a table_of_jwt to a jwt_token.\n\nThe `alg` argument specifies which hashing algorithm to use (`HS256`, `HS512`, `RS256`).\n\n### sample of table_of_jwt ###\n```\n{\n    \"header\": {\"typ\": \"JWT\", \"alg\": \"HS512\"},\n    \"payload\": {\"foo\": \"bar\"}\n}\n```\n\nverify\n------\n`syntax: local jwt_obj = jwt:verify(key, jwt_token [, claim_spec [, ...]])`\n\nverify 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.\n\nSee [Verification](#verification) for details on the format of `claim_spec` parameters.\n\n\nload \u0026 verify\n-------------\n```\nsyntax: local jwt_obj = jwt:load_jwt(jwt_token)\nsyntax: local verified = jwt:verify_jwt_obj(key, jwt_obj [, claim_spec [, ...]])\n```\n\n\n__verify = load_jwt +  verify_jwt_obj__\n\nload jwt, check for kid, then verify it with the correct key\n\n\n### sample of jwt_obj ###\n```\n{\n    \"raw_header\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\",\n    \"raw_payload: \"eyJmb28iOiJiYXIifQ\",\n    \"signature\": \"wrong-signature\",\n    \"header\": {\"typ\": \"JWT\", \"alg\": \"HS256\"},\n    \"payload\": {\"foo\": \"bar\"},\n    \"verified\": false,\n    \"valid\": true,\n    \"reason\": \"signature mismatched: wrong-signature\"\n}\n```\n\nsign-jwe\n--------\n\n`syntax: local jwt_token = jwt:sign(key, table_of_jwt)`\n\nsign a table_of_jwt to a jwt_token.\n\nThe `alg` argument specifies which hashing algorithm to use for encrypting key (`dir`).\nThe `enc` argument specifies which hashing algorithm to use for encrypting payload (`A128CBC-HS256`, `A256CBC-HS512`)\n\n### sample of table_of_jwt ###\n```\n{\n    \"header\": {\"typ\": \"JWE\", \"alg\": \"dir\", \"enc\":\"A128CBC-HS256\"},\n    \"payload\": {\"foo\": \"bar\"}\n}\n```\n\n[Back to TOC](#table-of-contents)\n\n\nVerification\n============\n\nBoth 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.\n\nThe signature of a `validator` function is:\n\n```\nfunction(val, claim, jwt_json)\n```\n\nWhere `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.\n\nA `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.\n\nA 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.\n\nMultiple `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.\n\n\n### sample `claim_spec` ###\n```\n{\n    sub = function(val) return string.match(\"^[a-z]+$\", val) end,\n    iss = function(val)\n        for _, value in pairs({ \"first\", \"second\" }) do\n            if value == val then return true end\n        end\n        return false\n    end,\n    __jwt = function(val, claim, jwt_json)\n        if val.payload.foo == nil or val.payload.bar == nil then\n            error(\"Need to specify either 'foo' or 'bar'\")\n        end\n    end\n}\n```\n\nJWT Validators\n--------------\n\nA library of helpful `validator` functions exists at `resty.jwt-validators`.  You can use this library by including:\n```\nlocal validators = require \"resty.jwt-validators\"\n```\n\nThe following functions are currently defined in the validator library.  Those marked with \"(opt)\" means that the same function exists named `opt_\u003cname\u003e` 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.\n\n#### `validators.chain(...)` ####\nReturns a validator that chains the given functions together, one after another - as long as they keep passing their checks.\n\n#### `validators.required(chain_function)` ####\nReturns 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.\n\n#### `validators.require_one_of(claim_keys)` ####\nReturns 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.\n\n#### `validators.check(check_val, check_function, name, check_type)` (opt)  ####\nReturns 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.\n\n#### `validators.equals(check_val)` (opt) ####\nReturns a validator that checks if a value exactly equals (using `==`) the given check_value. The value of `check_val` cannot be nil.\n\n#### `validators.matches(pattern)` (opt) ####\nReturns a validator that checks if a value matches the given pattern (using `string.match`).  The value of `pattern` must be a string.\n\n#### `validators.any_of(check_values, check_function, name, check_type, table_type)` (opt) ####\nReturns 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.\n\n#### `validators.equals_any_of(check_values)` (opt) ####\nReturns a validator that checks if a value exactly equals any of the given `check_values`.\n\n#### `validators.matches_any_of(patterns)` (opt) ####\nReturns a validator that checks if a value matches any of the given `patterns`.\n\n#### `validators.contains_any_of(check_values,name)` (opt) ####\nReturns 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`.\n\n#### `validators.greater_than(check_val)` (opt) ####\nReturns a validator that checks how a value compares (numerically, using `\u003e`) to a given `check_value`.  The value of `check_val` cannot be `nil` and must be a number.\n\n#### `validators.greater_than_or_equal(check_val)` (opt) ####\nReturns a validator that checks how a value compares (numerically, using `\u003e=`) to a given `check_value`.  The value of `check_val` cannot be `nil` and must be a number.\n\n#### `validators.less_than(check_val)` (opt) ####\nReturns a validator that checks how a value compares (numerically, using `\u003c`) to a given `check_value`.  The value of `check_val` cannot be `nil` and must be a number.\n\n#### `validators.less_than_or_equal(check_val)` (opt) ####\nReturns a validator that checks how a value compares (numerically, using `\u003c=`) to a given `check_value`.  The value of `check_val` cannot be `nil` and must be a number.\n\n#### `validators.is_not_before()` (opt) ####\nReturns a validator that checks if the current time is not before the tested value within the system's leeway.  This means that:\n```\nval \u003c= (system_clock() + system_leeway).\n```\n\n#### `validators.is_not_expired()` (opt) ####\nReturns 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:\n```\nval \u003e (system_clock() - system_leeway).\n```\n\n#### `validators.is_at()` (opt) ####\nReturns a validator that checks if the current time is the same as the tested value within the system's leeway.  This means that:\n```\nval \u003e= (system_clock() - system_leeway) and val \u003c= (system_clock() + system_leeway).\n```\n\n#### `validators.set_system_leeway(leeway)` ####\nA function to set the leeway (in seconds) used for `is_not_before` and `is_not_expired`.  The default is to use `0` seconds\n\n#### `validators.set_system_clock(clock)` ####\nA function to set the system clock used for `is_not_before` and `is_not_expired`.  The default is to use `ngx.now`\n\n### sample `claim_spec` using validators ###\n```\nlocal validators = require \"resty.jwt-validators\"\nlocal claim_spec = {\n    sub = validators.opt_matches(\"^[a-z]+$),\n    iss = validators.equals_any_of({ \"first\", \"second\" }),\n    __jwt = validators.require_one_of({ \"foo\", \"bar\" })\n}\n```\n\n\nLegacy/Timeframe options\n------------------------\n\nIn 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.\n\nWhen 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.\n\n* `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.\n\n    * 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.\n\n    * When none of the `nbf` and `exp` claims can be found, verification will fail.\n\n    * `nbf` and `exp` claims are expected to be expressed in the jwt as numerical values. Wouldn't that be the case, verification will fail.\n\n    * Specifying this option is equivalent to calling:\n      ```\n      validators.set_system_leeway(leeway)\n      ```\n\n      and specifying as a `claim_spec`:\n      ```\n      {\n        __jwt = validators.require_one_of({ \"nbf\", \"exp\" }),\n        nbf = validators.opt_is_not_before(),\n        exp = validators.opt_is_not_expired()\n      }\n      ```\n\n* `require_nbf_claim`: Express if the `nbf` claim is optional or not. Value should be a boolean.\n\n    * When this validation option is set to `true` and no `lifetime_grace_period` has been specified, a zero (`0`) leeway is implied.\n\n    * Specifying this option is equivalent to specifying as a `claim_spec`:\n      ```\n      {\n        nbf = validators.is_not_before(),\n      }\n      ```\n\n* `require_exp_claim`: Express if the `exp` claim is optional or not. Value should be a boolean.\n\n    * When this validation option is set to `true` and no `lifetime_grace_period` has been specified, a zero (`0`) leeway is implied.\n\n    * Specifying this option is equivalent to specifying as a `claim_spec`:\n      ```\n      {\n        exp = validators.is_not_expired(),\n      }\n      ```\n\n* `valid_issuers`: Whitelist the vetted issuers of the jwt. Value should be a array of strings.\n\n    * 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.\n\n    * `iss` claim is expected to be expressed in the jwt as a string. Wouldn't that be the case, verification will fail.\n\n    * Specifying this option is equivalent to specifying as a `claim_spec`:\n      ```\n      {\n        iss = validators.equals_any_of(valid_issuers),\n      }\n      ```\n\n\n### sample of validation_options usage ###\n```\nlocal jwt_obj = jwt:verify(key, jwt_token,\n    {\n        lifetime_grace_period = 120,\n        require_exp_claim = true,\n        valid_issuers = { \"my-trusted-issuer\", \"my-other-trusteed-issuer\" }\n    }\n)\n```\n\n\n\nExamples\n========\n* [JWT Auth With Query and Cookie](examples/README.md#jwt-auth-using-query-and-cookie)\n* [JWT Auth With KID and Store Your Key in Redis](examples/README.md#jwt-auth-with-kid-and-store-keys-in-redis)\n\n[Back to TOC](#table-of-contents)\n\n\nInstallation\n============\n\nUsing Luarocks\n```bash\nluarocks install lua-resty-jwt\n```\n\nIt is recommended to use the latest [ngx_openresty bundle](http://openresty.org) directly.\n\nAlso, You need to configure\nthe [lua_package_path](https://github.com/openresty/lua-nginx-module#lua_package_path) directive to\nadd the path of your lua-resty-jwt source tree to ngx_lua's Lua module search path, as in\n\n```nginx\n    # nginx.conf\n    http {\n        lua_package_path \"/path/to/lua-resty-jwt/lib/?.lua;;\";\n        ...\n    }\n```\n\nand then load the library in Lua:\n\n```lua\n    local jwt = require \"resty.jwt\"\n```\n\n\n[Back to TOC](#table-of-contents)\n\nTesting With Docker\n===================\n\n```\n./ci script\n```\n\n[Back to TOC](#table-of-contents)\n\n\nSee Also\n========\n* the ngx_lua module: http://wiki.nginx.org/HttpLuaModule\n\n[Back to TOC](#table-of-contents)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskylothar%2Flua-resty-jwt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskylothar%2Flua-resty-jwt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskylothar%2Flua-resty-jwt/lists"}