{"id":25999592,"url":"https://github.com/anvouk/lua-resty-jwt-verification","last_synced_at":"2026-05-19T06:05:19.174Z","repository":{"id":279082239,"uuid":"805940785","full_name":"anvouk/lua-resty-jwt-verification","owner":"anvouk","description":"JWT verification library for OpenResty with JWKS integration","archived":false,"fork":false,"pushed_at":"2025-10-27T22:00:27.000Z","size":204,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-27T22:23:36.565Z","etag":null,"topics":["jose","jwe","jwks","jws","jwt","lua","luajit","openresty"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anvouk.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-05-25T22:51:21.000Z","updated_at":"2025-10-27T20:05:38.000Z","dependencies_parsed_at":"2025-09-24T22:09:12.296Z","dependency_job_id":"90fab346-0a8f-4bca-af51-786177ffc326","html_url":"https://github.com/anvouk/lua-resty-jwt-verification","commit_stats":null,"previous_names":["anvouk/lua-resty-jwt-verification"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/anvouk/lua-resty-jwt-verification","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anvouk%2Flua-resty-jwt-verification","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anvouk%2Flua-resty-jwt-verification/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anvouk%2Flua-resty-jwt-verification/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anvouk%2Flua-resty-jwt-verification/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anvouk","download_url":"https://codeload.github.com/anvouk/lua-resty-jwt-verification/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anvouk%2Flua-resty-jwt-verification/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33204089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T09:27:30.708Z","status":"online","status_checked_at":"2026-05-19T02:00:06.763Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["jose","jwe","jwks","jws","jwt","lua","luajit","openresty"],"created_at":"2025-03-05T18:40:38.979Z","updated_at":"2026-05-19T06:05:19.168Z","avatar_url":"https://github.com/anvouk.png","language":"Lua","funding_links":[],"categories":["Libraries"],"sub_categories":[],"readme":"# JWT verification for openresty\n\nJWT verification library for OpenResty.\n\n![OPM](https://img.shields.io/opm/v/anvouk/lua-resty-jwt-verification) ![LuaRocks](https://img.shields.io/luarocks/v/anvouk/lua-resty-jwt-verification) ![Tests](https://github.com/anvouk/lua-resty-jwt-verification/actions/workflows/tests.yml/badge.svg)\n\n## Table of Contents\n\n- [Description](#description)\n- [Install](#install)\n- [Status](#status)\n- [Library non-goals](#library-non-goals)\n- [Differences from lua-resty-jwt](#differences-from-lua-resty-jwt)\n- [Types](#types)\n- [Supported features](#supported-features)\n  - [JWS Verification](#jws-verification)\n  - [JWE Decryption](#jwe-decryption)\n  - [JWKS retrieval cache strategies](#jwks-retrieval-cache-strategies)\n- [JWT verification usage](#jwt-verification-usage)\n  - [jwt.decode_header_unsafe](#jwtdecode_header_unsafe)\n  - [jwt.verify](#jwtverify)\n  - [jwt.decrypt](#jwtdecrypt)\n- [JWKS verification usage](#jwks-verification-usage)\n  - [jwks.init](#jwksinit)\n  - [jwks.set_http_timeouts_ms](#jwksset_http_timeouts_ms)\n  - [jwks.set_http_ssl_verify](#jwksset_http_ssl_verify)\n  - [jwks.set_cache_ttl](#jwksset_cache_ttl)\n  - [jwks.fetch_jwks](#jwksfetch_jwks)\n  - [jwks.verify_jwt_with_jwks](#jwksverify_jwt_with_jwks)\n  - [jwks.decrypt_jwt_with_jwks](#jwksdecrypt_jwt_with_jwks)\n- [RFCs used as reference](#rfcs-used-as-reference)\n- [Run tests](#run-tests)\n  - [Setup](#setup)\n  - [Run](#run)\n- [Run benchmarks](#run-benchmarks)\n\n## Description\n\nJWT verification library for OpenResty.\n\nThe project's goal is to be a modern and slimmer replacement for [lua-resty-jwt](https://github.com/cdbattags/lua-resty-jwt/)\nwith built-in support for JWKS.\n\nThis project does not provide JWT manipulation or creation features: you can only verify/decrypt tokens.\n\n## Install\n\nInstall with [OPM](https://opm.openresty.org/package/anvouk/lua-resty-jwt-verification) (recommended):\n\n```bash\nopm get anvouk/lua-resty-jwt-verification\n```\n\nOr with [luarocks](https://luarocks.org/modules/anvouk/lua-resty-jwt-verification):\n\n```bash\nluarocks install lua-resty-jwt-verification\n```\n\n## Status\n\nFeature complete and fully working.\n\nAPI is stable and breaking changes will follow SEMVER. Will tag 1.0 relatively soon.\n\n## Library non-goals\n\n- JWT creation/modification\n- Feature complete for the sake of RFCs completeness.\n- Senseless and unsafe RFCs features (e.g. alg none) won't be implemented.\n\n## Differences from lua-resty-jwt\n\nMain differences are:\n- No JWT manipulation of any kind (you can only decrypt/verify them)\n- Simpler internal structure reliant on more recent [lua-resty-openssl](https://github.com/fffonion/lua-resty-openssl) and OpenSSL versions.\n- Supports different JWE algorithms (see tables above).\n- Automatic JWT verification given JWKS HTTP endpoint.\n\nIf any of the points above are a problem, or you need compatibility with older OpenResty versions, I\nrecommend sticking with [lua-resty-jwt](https://github.com/cdbattags/lua-resty-jwt/).\n\n## Types\n\nTypes and null checks are provided with extensive use of [EmmyLua annotations](https://luals.github.io/wiki/annotations).\n\nIDEs plugin integrations for EmmyLua:\n- [Idea](https://github.com/EmmyLua/Intellij-EmmyLua2)\n- [VSCode](https://github.com/EmmyLua/VSCode-EmmyLua)\n\nThe file `ngx.d.lua` in the project's root provides some `ngx` stubs.\n\n## Supported features\n\n- JWS verification: with symmetric or asymmetric keys.\n- JWE decryption: with symmetric or asymmetric keys.\n- Asymmetric keys format supported:\n  - PEM\n  - DER\n  - JWK\n- JWT claims validation.\n- Automatic JWKS fetching and JWT validation.\n  - optional caching strategies.\n- Nested JWTs (JWS in JWE)\n\n### JWS Verification\n\n|  Claims  |    Implemented     |\n|:--------:|:------------------:|\n|   alg    | :white_check_mark: |\n|   jku    |        :x:         |\n|   jwk    |        :x:         |\n|   kid    | :white_check_mark: |\n|   x5u    |        :x:         |\n|   x5c    |        :x:         |\n|   x5t    |        :x:         |\n| x5t#S256 |        :x:         |\n|   typ    | :white_check_mark: |\n|   cty    |        :x:         |\n|   crit   | :white_check_mark: |\n\n|   alg   |    Implemented     | JOSE Implementation Requirements | Requirements  |\n|:-------:|:------------------:|:--------------------------------:|:-------------:|\n|  HS256  | :white_check_mark: |             Required             |               |\n|  HS384  | :white_check_mark: |             Optional             |               |\n|  HS512  | :white_check_mark: |             Optional             |               |\n|  RS256  | :white_check_mark: |           Recommended            |               |\n|  RS384  | :white_check_mark: |             Optional             |               |\n|  RS512  | :white_check_mark: |             Optional             |               |\n|  ES256  | :white_check_mark: |           Recommended+           |               |\n|  ES384  | :white_check_mark: |             Optional             |               |\n|  ES512  | :white_check_mark: |             Optional             |               |\n|  PS256  | :white_check_mark: |             Optional             |               |\n|  PS384  | :white_check_mark: |             Optional             |               |\n|  PS512  | :white_check_mark: |             Optional             |               |\n|  none   |        :x:         |             Optional             |               |\n|  EdDSA  |        :x:         |            Deprecated            |               |\n| ES256K  | :white_check_mark: |             Optional             |               |\n| Ed25519 | :white_check_mark: |             Optional             | *OpenSSL 3.0+ |\n|  Ed448  | :white_check_mark: |             Optional             |               |\n\n### JWE Decryption\n\n|  Claims  |    Implemented     |\n|:--------:|:------------------:|\n|   alg    | :white_check_mark: |\n|   enc    | :white_check_mark: |\n|   zip    |        :x:         |\n|   jku    |        :x:         |\n|   jwk    |        :x:         |\n|   kid    | :white_check_mark: |\n|   x5u    |        :x:         |\n|   x5c    |        :x:         |\n|   x5t    |        :x:         |\n| x5t#S256 |        :x:         |\n|   typ    | :white_check_mark: |\n|   cty    | :white_check_mark: |\n|   crit   | :white_check_mark: |\n\n| kty |    Implemented     | JOSE Implementation Requirements |\n|:---:|:------------------:|:--------------------------------:|\n| EC  | :white_check_mark: |           Recommended+           |\n| RSA | :white_check_mark: |             Required             |\n| oct | :white_check_mark: |             Required             |\n| OKP | :white_check_mark: |             Optional             |\n\n|        alg         |    Implemented     | JOSE Implementation Requirements | Requirements  |\n|:------------------:|:------------------:|:--------------------------------:|:-------------:|\n|       RSA1_5       |        :x:         |           Recommended-           |               |\n|      RSA-OAEP      | :white_check_mark: |           Recommended+           |               |\n|    RSA-OAEP-256    | :white_check_mark: |             Optional             |               |\n|    RSA-OAEP-384    | :white_check_mark: |             Optional             |               |\n|    RSA-OAEP-512    | :white_check_mark: |             Optional             |               |\n|       A128KW       | :white_check_mark: |           Recommended            | *OpenSSL 3.0+ |\n|       A192KW       | :white_check_mark: |             Optional             | *OpenSSL 3.0+ |\n|       A256KW       | :white_check_mark: |           Recommended            | *OpenSSL 3.0+ |\n|        dir         | :white_check_mark: |           Recommended            |               |\n|      ECDH-ES       | :white_check_mark: |           Recommended+           |               |\n|   ECDH-ES+A128KW   | :white_check_mark: |           Recommended            | *OpenSSL 3.0+ |\n|   ECDH-ES+A192KW   | :white_check_mark: |             Optional             | *OpenSSL 3.0+ |\n|   ECDH-ES+A256KW   | :white_check_mark: |           Recommended            | *OpenSSL 3.0+ |\n|     A128GCMKW      |        :x:         |             Optional             |               |\n|     A192GCMKW      |        :x:         |             Optional             |               |\n|     A256GCMKW      |        :x:         |             Optional             |               |\n| PBES2-HS256+A128KW |        :x:         |             Optional             |               |\n| PBES2-HS384+A192KW |        :x:         |             Optional             |               |\n| PBES2-HS512+A256KW |        :x:         |             Optional             |               |\n\n\u003e *The first official release of OpenResty including OpenSSL 3.0+ is [OpenResty 1.27.1.1](https://openresty.org/en/ann-1027001001.html)\n\u003e which shipped with OpenSSL 3.0.15 (Yes, the [godawful slow OpenSSL 3.0 series...](https://github.com/openssl/openssl/issues/17064)).\n\u003e\n\u003e So, please, go with [OpenResty 1.27.1.2](https://openresty.org/en/ann-1027001002.html) as a minimum, which shipped\n\u003e with OpenSSL 3.4.1.\n\n|      enc      |    Implemented     | JOSE Implementation Requirements |\n|:-------------:|:------------------:|:--------------------------------:|\n| A128CBC-HS256 | :white_check_mark: |             Required             |\n| A192CBC-HS384 | :white_check_mark: |             Optional             |\n| A256CBC-HS512 | :white_check_mark: |             Required             |\n|    A128GCM    | :white_check_mark: |           Recommended            |\n|    A192GCM    | :white_check_mark: |             Optional             |\n|    A256GCM    | :white_check_mark: |           Recommended            |\n\n## JWKS retrieval cache strategies\n\n|   Cache Strategy    |    Implemented     |\n|:-------------------:|:------------------:|\n|      no cache       | :white_check_mark: |\n| local (shared_dict) | :white_check_mark: |\n\n## JWT verification usage\n\n### jwt.decode_header_unsafe\n\n**syntax**: *header, err = jwt.decode_header_unsafe(token)*\n\nRead a jwt header and convert it to a lua table.\n\n\u003e **Important**: this method does not validate JWT signature! Only use if you need to inspect the token's header\n\u003e without having to perform the full validation.\n\n```lua\nlocal jwt = require(\"resty.jwt-verification\")\n\nlocal token = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2NDkwNzJ9._MwFdsBPSyci9iARpoAaulReGcn1q7mKiPZjR2JDvdY\"\nlocal header, err = jwt.decode_header_unsafe(token)\nif not header then\n    return nil, \"malformed jwt: \" .. err\nend\nprint(\"alg: \" .. header.alg) -- alg: HS256\n```\n\n### jwt.verify\n\n**syntax**: *decoded_token, err = jwt.verify(token, secret, options?)*\n\nValidate a JWS token and convert it to a lua table.\n\nThe optional parameter `options` can be passed to configure the token validator. Valid fields are:\n- `valid_signing_algorithms` (dict\u003cstring, string\u003e | nil): a dict containing allowed `alg` claims used to validate the JWT.\n- `typ` (string | nil): if non-null, ensure JWT claim `typ` matches the passed value.\n- `issuer` (string | nil): if non-null, ensure JWT claim `iss` matches the passed value.\n- `audiences` (string | table | nil): if non-null, ensure JWT claim `aud` matches one of the supplied values.\n- `subject` (string | nil): if non-null, ensure JWT claim `sub` matches the passed value.\n- `jwtid` (string | nil): if non-null, ensure JWT claim `jti` matches the passed value.\n- `ignore_not_before` (bool): If true, the JWT claim `nbf` will be ignored.\n- `ignore_expiration` (bool): If true, the JWT claim `exp` will be ignored.\n- `current_unix_timestamp` (datetime | nil): the JWT `nbf` and `exp` claims will be validated against this timestamp. If null,\n  will use the current datetime supplied by `ngx.time()`.\n- `timestamp_skew_seconds` (int): How many seconds of leeway can the library use to check token expiration against current\n  time. Useful when clocks are not always exactly synchronized. Setting this value too high may pose security issues.\n\nDefault values for `options` fields:\n```lua\nlocal verify_default_options = {\n    valid_signing_algorithms = {\n        [\"HS256\"]=\"HS256\", [\"HS384\"]=\"HS384\", [\"HS512\"]=\"HS512\",\n        [\"RS256\"]=\"RS256\", [\"RS384\"]=\"RS384\", [\"RS512\"]=\"RS512\",\n        [\"ES256\"]=\"ES256\", [\"ES384\"]=\"ES384\", [\"ES512\"]=\"ES512\",\n        [\"PS256\"]=\"PS256\", [\"PS384\"]=\"PS384\", [\"PS512\"]=\"PS512\",\n        [\"ES256K\"]=\"ES256K\", [\"Ed25519\"]=\"Ed25519\", [\"Ed448\"]=\"Ed448\",\n    },\n    typ = nil,\n    issuer = nil,\n    audiences = nil,\n    subject = nil,\n    jwtid = nil,\n    ignore_not_before = false,\n    ignore_expiration = false,\n    current_unix_timestamp = nil,\n    timestamp_skew_seconds = 1,\n}\n```\n\nMinimal example with symmetric keys:\n```lua\nlocal jwt = require(\"resty.jwt-verification\")\n\nlocal token = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2NTUwMTV9.NuEhIzUuufJgPZ8CmCPnD4Vrw7EnTyWD8bGtYCwuDZ0\"\nlocal decoded_token, err = jwt.verify(token, \"superSecretKey\")\nif not decoded_token then\n    return nil, \"invalid jwt: \" .. err\nend\nprint(decoded_token.header.alg) -- HS256\nprint(decoded_token.payload.foo) -- bar\n```\n\nMinimal example with asymmetric keys:\n```lua\nlocal jwt = require(\"resty.jwt-verification\")\n\nlocal token = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2Njg2Mzd9.H6PE-zLizMMqefx8DG4X5glVjyxR9UNT225Tq2yufHhu4k9K0IGttpykjMCG8Ck_4Qt2ezEWIgoiWhSn1rv_zwxe7Pv-B09fDs7h1hbASi5MZ0YVAmK9ID1RCKM_NTBEnPLot_iopKZRj2_J5F7lvXwJDZSzEAFJZdrgjKeBS4saDZAv7SIL9Nk75rdhgY-RgRwsjmTYSksj7eioRJJLHifrMnlQDbdrBD5_Qk5tD6VPcssO-vIVBUAYrYYTa7M7A_v47UH84zDtzNYBbk9NrDbyq5-tYs0lZwNhIX8t-0VAxjuCyrrGZvv8_O01pdi90kQmntFIbaiDiD-1WlGcGA\"\nlocal decoded_token, err = jwt.verify(token, \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXFhNyhFWuWtFSJqfOAw\\np42lLIn9kB9oaciiKgNAYZ8SYw5t9Fo+Zh7IciVijn+cVS2/aoBNg2HhfdYgfpQ/\\nsb6jwbRqFMln2GmG+X2aJ2wXMJ/QfxrPFdO9L36bAEwkubUTYXwgMSm1KqWRN8xX\\n+oBu+dbyzw7iUbrmw0ybzXKZLJvetCvmt0reU5TvdwoczOWFBSKeYnzBrC6hISD8\\n8TYDJ4tiw1EWVOupQGqgel0KjC7iwdIYi7PROn6/1MMnF48zlBbT/7/zORj84Z/y\\nDnmxZu1MQ07kHqXDRYumSfCerg5Xw5vde7Tz8O0TWtaYV3HJXNa0VpN5OI3L4y7P\\nhwIDAQAB\\n-----END PUBLIC KEY-----\")\nif not decoded_token then\n    return nil, \"invalid jwt: \" .. err\nend\nprint(decoded_token.header.alg) -- RS256\nprint(decoded_token.payload.foo) -- bar\n```\n\nExamples with custom `options`:\n```lua\nlocal jwt = require(\"resty.jwt-verification\")\n\nlocal token = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2NTUwMTV9.NuEhIzUuufJgPZ8CmCPnD4Vrw7EnTyWD8bGtYCwuDZ0\"\nlocal decoded_token, err = jwt.verify(token, \"superSecretKey\", {\n    valid_signing_algorithms = {[\"HS256\"]=\"HS256\", [\"HS384\"]=\"HS384\", [\"HS512\"]=\"HS512\"}, -- only allow HS family algs\n    audiences = {\"user\", \"admin\"}, -- `aud` must be one of the following\n    ignore_not_before = true -- ignore `nbf` claim (not recommended)\n})\nif not decoded_token then\n    return nil, \"invalid jwt: \" .. err\nend\nprint(decoded_token.header.alg) -- HS256\nprint(decoded_token.payload.foo) -- bar\n```\n\n### jwt.decrypt\n\n**syntax**: *decoded_token, err = jwt.decrypt(token, secret, options?)*\n\nDecrypt and validate a JWE token and convert it to a lua table.\n\nThe optional parameter `options` can be passed to configure the token validator. Valid fields are:\n- `valid_encryption_alg_algorithms` (dict\u003cstring, string\u003e | nil): a dict containing allowed `alg` claims used to decrypt the JWT.\n- `valid_encryption_enc_algorithms` (dict\u003cstring, string\u003e | nil): a dict containing allowed `enc` claims used to decrypt the JWT.\n- `typ` (string | nil): if non-null, ensure JWT claim `typ` matches the passed value.\n- `issuer` (string | nil): if non-null, ensure JWT claim `iss` matches the passed value.\n- `audiences` (string | table | nil): if non-null, ensure JWT claim `aud` matches one of the supplied values.\n- `subject` (string | nil): if non-null, ensure JWT claim `sub` matches the passed value.\n- `jwtid` (string | nil): if non-null, ensure JWT claim `jti` matches the passed value.\n- `ignore_not_before` (bool): If true, the JWT claim `nbf` will be ignored.\n- `ignore_expiration` (bool): If true, the JWT claim `exp` will be ignored.\n- `current_unix_timestamp` (datetime | nil): the JWT `nbf` and `exp` claims will be validated against this timestamp. If null,\n  will use the current datetime supplied by `ngx.time()`.\n- `timestamp_skew_seconds` (int): How many seconds of leeway can the library use to check token expiration against current\n  time. Useful when clocks are not always exactly synchronized. Setting this value too high may pose security issues.\n- `allow_nested_jwt` (bool): Allows verification of jwts containing another jwt (aka nested jwts or jwt-in-jwt). This is opt-in\n  as default since the claims to validate are always inside the innermost jwt and WILL NOT be automatically validated. It's up\n  to you to recursively validate the inner jwts returned as a string in the `payload` field by this lib. A nested\n  jwt MUST contain the `cty` header key set to `JWT` to be recognized as such.\n\nDefault values for `options` fields:\n```lua\nlocal decrypt_default_options = {\n    valid_encryption_alg_algorithms = {\n        [\"RSA-OAEP\"]=\"RSA-OAEP\",\n        [\"RSA-OAEP-256\"]=\"RSA-OAEP-256\", [\"RSA-OAEP-384\"]=\"RSA-OAEP-384\", [\"RSA-OAEP-512\"]=\"RSA-OAEP-512\",\n        [\"A128KW\"]=\"A128KW\", [\"A192KW\"]=\"A192KW\", [\"A256KW\"]=\"A256KW\",\n        [\"dir\"]=\"dir\",\n        [\"ECDH-ES\"]=\"ECDH-ES\",\n        [\"ECDH-ES+A128KW\"]=\"ECDH-ES+A128KW\",\n        [\"ECDH-ES+A192KW\"]=\"ECDH-ES+A192KW\",\n        [\"ECDH-ES+A256KW\"]=\"ECDH-ES+A256KW\",\n    },\n    valid_encryption_enc_algorithms = {\n        [\"A128CBC-HS256\"]=\"A128CBC-HS256\",\n        [\"A192CBC-HS384\"]=\"A192CBC-HS384\",\n        [\"A256CBC-HS512\"]=\"A256CBC-HS512\",\n        [\"A128GCM\"]=\"A128GCM\",\n        [\"A192GCM\"]=\"A192GCM\",\n        [\"A256GCM\"]=\"A256GCM\",\n    },\n    typ = nil,\n    issuer = nil,\n    audiences = nil,\n    subject = nil,\n    jwtid = nil,\n    ignore_not_before = false,\n    ignore_expiration = false,\n    current_unix_timestamp = nil,\n    timestamp_skew_seconds = 1,\n    allow_nested_jwt = false,\n}\n```\n\nMinimal example with symmetric keys:\n```lua\nlocal jwt = require(\"resty.jwt-verification\")\n\nlocal token = \"eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.zAIq7qVAEO-eCG6gOdd3ld8_IHzeq3UlaWLHF2IDn6nNUuHh5n_i4w.5CM864cgiBgFPwluW4ViRg.mUeX7zHDVNsXhys0XO5S4w.t3yAR_HU0GDTEyCbpRa6BQ\"\nlocal decoded_token, err = jwt.decrypt(token, \"superSecretKey12\")\nif not decoded_token then\n    return nil, \"invalid jwt: \" .. err\nend\nprint(decoded_token.header.alg) -- A128KW\nprint(decoded_token.header.enc) -- A128CBC-HS256\nprint(decoded_token.payload.foo) -- bar\n```\n\nMinimal example with asymmetric keys in PEM format:\n```lua\nlocal jwt = require(\"resty.jwt-verification\")\n\nlocal token = \"eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkEyNTZHQ00iLCJlcGsiOnsieCI6IkFJdkVhSzVKZGl6d1I5ZFMzRUN2Y0dKMGNHWXNFejdpYWJwRUp1bE0tWDAiLCJjcnYiOiJYMjU1MTkiLCJrdHkiOiJPS1AifX0.QFfmPVYjk1PoyhE7elaDgUdUGGeAECLo7jB4ghq_8MIRXV3VKO1yAA.XITF2apHB5roeUsx.08T0gALwkb6Wibr2Og.IJoh3U_tspnMx_mWelRT5g\"\nlocal decoded_token, err = jwt.decrypt(token, \"-----BEGIN PRIVATE KEY-----\\nMC4CAQAwBQYDK2VuBCIEIMCxXl/FEuh3pGo1Z++QRs2vudqkGd63mK0Js0f6y+55\\n-----END PRIVATE KEY-----\", nil)\nif not decoded_token then\n    return nil, \"invalid jwt: \" .. err\nend\nprint(decoded_token.header.alg) -- ECDH-ES+A128KW\nprint(decoded_token.header.enc) -- A256GCM\nprint(decoded_token.payload.foo) -- bar\n```\n\nExamples with custom `options`:\n```lua\nlocal jwt = require(\"resty.jwt-verification\")\n\nlocal token = \"eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.zAIq7qVAEO-eCG6gOdd3ld8_IHzeq3UlaWLHF2IDn6nNUuHh5n_i4w.5CM864cgiBgFPwluW4ViRg.mUeX7zHDVNsXhys0XO5S4w.t3yAR_HU0GDTEyCbpRa6BQ\"\nlocal decoded_token, err = jwt.decrypt(token, \"superSecretKey12\", {\n    valid_encryption_alg_algorithms = {[\"A128KW\"]=\"A128KW\"}, -- only allow A128KW family algs (requires OpenSSL 3.0+)\n    valid_encryption_enc_algorithms = {[\"A128CBC-HS256\"]=\"A128CBC-HS256\"}, -- only allow A128CBC family encs\n    audiences = {\"user\", \"admin\"}, -- `aud` must be one of the following\n    ignore_not_before = true -- ignore `nbf` claim (not recommended)\n})\nif not decoded_token then\n    return nil, \"invalid jwt: \" .. err\nend\nprint(decoded_token.header.alg) -- A128KW\nprint(decoded_token.header.enc) -- A128CBC-HS256\nprint(decoded_token.payload.foo) -- bar\n```\n\n## JWKS verification usage\n\nThe `resty.jwt-verification-jwks` module implements automatic JWKS retrieval from an HTTP endpoint and subsequent JWT\nvalidation with fetched keys.\n\nThe `resty.jwt-verification-jwks-cache-*` modules implement optional JWKS caching strategies. Only one caching strategy\ncan be enabled at a time; if none are enabled, the JWKS endpoint will be called once for every JWT to validate.\n\n### jwks.init\n\n**syntax**: *ok, err = jwks.init(cache_strategy?)*\n\nInitialize the jwks module and optionally specify a caching strategy.\n\nThis function must be called only once and preferably in the `init_by_lua_file` section.\n\n```lua\nlocal jwks = require(\"resty.jwt-verification-jwks\")\n\n-- initalize without cache\nlocal ok, err = jwks.init(nil)\nif not ok then\n    ngx.say(\"Error initializing jwks module: \", err)\nend\n\n-- or ...\n\n-- initalize with local cache based on openresty shared mem dict.\n-- add this in the `http` section of your nginx config: `lua_shared_dict resty_jwt_verification_cache_jwks 10m;`\n-- see https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/#ngxshareddict\nlocal jwks_cache_local = require(\"resty.jwt-verification-jwks-cache-local\")\nlocal ok, err = jwks.init(jwks_cache_local)\nif not ok then\n    ngx.say(\"Error initializing jwks module: \", err)\nend\n```\n\nYou can implement your own cache and pass it in the init method instead. Here's an example how:\n\n```lua\nlocal my_cache = {}\n\n---Get cached entry string for key.\n---@param key string Cache key.\n---@return string|nil value Return cached result as string if present, nil otherwise.\nfunction my_cache.get(key)\n    -- TODO\nend\n\n---Cache data under key until expiry.\n---@param key string Cache key.\n---@param value string Cache value.\n---@param expiry integer Cache entry expiry in seconds.\n---@return boolean|nil ok true on success\n---@return string|nil err nil on success, error message otherwise.\nfunction my_cache.setex(key, value, expiry)\n    -- TODO\nend\n\nlocal ok, err = jwks.init(my_cache)\nif not ok then\n    ngx.say(\"Error initializing jwks module: \", err)\nend\n```\n\n### jwks.set_http_timeouts_ms\n\n**syntax**: *jwks.set_http_timeouts_ms(connect, send, read)*\n\nSet HTTP client timeouts in milliseconds used for fetching JWKS.\n\n```lua\nlocal jwks = require(\"resty.jwt-verification-jwks\")\n\njwks.set_http_timeouts_ms(5000, 5000, 5000)\n```\n\n### jwks.set_http_ssl_verify\n\n**syntax**: *jwks.set_http_ssl_verify(enabled)*\n\nEnable/disable TLS verification used by HTTP client for fetching JWKS.\n\nBy default, all TLS certificates are verified. If the JWKS endpoint is using self-signed certificates, either add\nthe respective root CA to the OS certs store or disable certificates verification with this endpoint (it's unsafe).\n\n```lua\nlocal jwks = require(\"resty.jwt-verification-jwks\")\n\njwks.set_http_ssl_verify(false)\n```\n\n### jwks.set_cache_ttl\n\n**syntax**: *jwks.set_http_ssl_verify(enabled)*\n\nChange the default cache TTL. Default value is 12 hours.\n\n\u003e **Note**: The cache ttl can only be used when the jwks module has been initialized with a cache.\n\u003e See [how to enable caching](#jwksinit).\n\n```lua\nlocal jwks = require(\"resty.jwt-verification-jwks\")\n\njwks.set_cache_ttl(2 * 3600) -- 2h\n```\n\n### jwks.fetch_jwks\n\n**syntax**: *payload, err = jwks.fetch_jwks(endpoint)*\n\nManually fetch JWKS from HTTP endpoint; the returned payload, in case of success, is the HTTP response body as string:\nNo check is performed whatsoever whether the payload contains JWKS or something else.\n\nIf a caching strategy has been enabled, the endpoint will try to fetch it from the cache first. After a cache miss and\nsuccessful JWKS retrieval via HTTP, the cache will be updated with the result.\n\n```lua\nlocal jwks = require(\"resty.jwt-verification-jwks\")\n\npayload, err = jwks.fetch_jwks(\"https://www.googleapis.com/oauth2/v3/certs\")\nif payload == nil then\n    print(\"failed fetching JWKS: \", err)\n    return\nend\nprint(payload) -- '{\"keys\":[{\"alg\":\"RS256\",\"e\":\"AQAB\",\"kid\":\"882503a5fd56e9f734dfba5c50d7bf48db284ae9\",\"kty\":\"RSA\",\"n\":\"woRUr445_ODXrFeynz5L208aJkABOKQHEzbfGM_V1ijkYZWZKY0PXKPP_wRKcE4C6OyjDNd5gHh3dF5QsVhVDZCfR9QjTf94o4asngrHzdOcfQ0pZIvzu_vzaVG82VGLM-2rKQp8uz06A6TbUzbIv9wQ8wQpYDIdujNkLqL22Mkb2drPxm9Y9I05PmVdkkvAbu4Q_KRJWxykOigHp-hVBmpYS2P3xuX56gM7ZRcXXJKKUfrGel4nDhSIAAD1wBNcVVgKbb0TYfZmVpRSCji_b6JHjqYhYjUasdotYJzWl7quAFsN_X_4j-cHZ30OS81j--OiIxWpL11y1kcbE0u-Dw\",\"use\":\"sig\"},{\"n\":\"m7GlcF1ExRB4braT7sDnZvlY3wpqX9krkVRqcVA-m43FWFYBtuSpd-lc0EV8R8TO180y0tSgJc7hviI1IBJQlNa7XkjVGhY0ZFUp5rTpC45QbA9Smo4CLa5HQIf-69rkkovjFNMuDQvNiYCgRPLyRjmQbN2uHl4fU3hhf5qFqKTKo7eLCZiEMjrOkTXziA7xJJigUGe-ab8U-AXNH1fnCbejzHEIxL0eUG_4r4xddImOxETDO5T65AQCeqs7vtYos2xq5SLFuaUsithRQ-IMm3OlcVhMjBYt6uvGS6IdMjKon4wThCxEqAEXg0nahiGjnQCW176SNF152__TOjQVwQ\",\"alg\":\"RS256\",\"kty\":\"RSA\",\"use\":\"sig\",\"kid\":\"8e8fc8e556f7a76d08d35829d6f90ae2e12cfd0d\",\"e\":\"AQAB\"}]}'\n```\n\n### jwks.verify_jwt_with_jwks\n\n**syntax**: *jwt, err = jwks.verify_jwt_with_jwks(jwt_token, jwks_endpoint, jws_options?)*\n\nGiven a signed jwt_token as a string, verify its signature with JWKS provided by the HTTP service found at jwks_endpoint.\n\nOn success, the verified JWT is returned as a lua table, otherwise nil and an error are returned.\n\nThe optional parameter `jws_options` can be passed to configure the token validator when calling [jwt.verify](#jwtverify)\nafter having successfully fetched the JWKS. See [jwt.verify](#jwtverify) respective docs for more info about which options\ncan be passed.\n\n```lua\nlocal jwks = require(\"resty.jwt-verification-jwks\")\n\njwt, err = jwks.verify_jwt_with_jwks(\"\u003cMY_JWT\u003e\", \"http://myservice:8888/.well-known/jwks.json\", nil)\nif jwt == nil then\n    print(\"failed verifying jwt: \", err)\n    return\nend\nprint(jwt.header.alg)\nprint(tostring(jwt.payload))\n```\n\n### jwks.decrypt_jwt_with_jwks\n\n**syntax**: *jwt, err = jwks.decrypt_jwt_with_jwks(jwt_token, jwks_endpoint, jwe_options?)*\n\nGiven an encrypted jwt_token as a string, decrypt it with JWKS provided by the HTTP service found at jwks_endpoint.\n\nOn success, the decrypted JWT is returned as a lua table, otherwise nil and an error are returned.\n\nThe optional parameter `jwe_options` can be passed to configure the token validator when calling [jwt.decrypt](#jwtdecrypt)\nafter having successfully fetched the JWKS. See [jwt.decrypt](#jwtdecrypt) respective docs for more info about which options\ncan be passed.\n\n```lua\nlocal jwks = require(\"resty.jwt-verification-jwks\")\n\njwt, err = jwks.decrypt_jwt_with_jwks(\"\u003cMY_JWT\u003e\", \"http://myservice:8888/.well-known/jwks.json\", nil)\nif jwt == nil then\n    print(\"failed decrypting jwt: \", err)\n    return\nend\nprint(jwt.header.alg)\nprint(jwt.header.enc)\nprint(tostring(jwt.payload))\n```\n\n## RFCs used as reference\n\n- [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515) JSON Web Signature (JWS)\n- [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516) JSON Web Encryption (JWE)\n- [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517) JSON Web Key (JWK)\n- [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518) JSON Web Algorithms (JWA)\n- [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519) JSON Web Token (JWT)\n- [RFC 7520](https://datatracker.ietf.org/doc/html/rfc7520) Examples of Protecting Content Using JSON Object Signing and Encryption (JOSE)\n- [IANA JOSE assignments](https://www.iana.org/assignments/jose/jose.xhtml)\n\n## Run tests\n\n### Setup\n\nInstall test suit:\n```bash\nsudo cpan Test::Nginx\n```\n\nInstall openresty: see https://openresty.org/en/linux-packages.html\n\n### Run\n\n```bash\nexport PATH=/usr/local/openresty/nginx/sbin:$PATH\nprove -r t\n```\n\n## Run benchmarks\n\nThe testsuite `Test::Nginx` has built-in benchmarking integration with [weighttp](https://redmine.lighttpd.net/projects/weighttp/wiki).\n\n### Install weighttp\n\nSource install:\n```bash\ncd /opt\nsudo git clone https://github.com/lighttpd/weighttp.git --single-branch --depth 1\nsudo chown -R \"$USER\" weighttp\ncd ./weighttp\nsh autogen.sh\n./configure --prefix=/opt/weighttp\nmake\nmake install\n```\n\nAdd to PATH:\n```bash\necho 'export PATH=\"$PATH:/opt/weighttp/bin\"' \u003e\u003e \"/home/$USER/.bashrc\"\nexec bash\nweighttp -V\n```\n\n## Increase `sysctl` limits\n\nIf you plant to stress test the library, you may need to increase system limits.\n\n```bash\ncat \u003e /etc/sysctl.d/openresty-benchmarks.conf \u003c\u003c EOF\nnet.ipv4.ip_local_port_range=2048 65535\n\nnet.ipv4.tcp_tw_reuse=1\n\nnet.core.netdev_max_backlog=2000\nnet.ipv4.tcp_max_syn_backlog=2048\nEOF\n\n# apply changes\nsudo sysctl -p /etc/sysctl.d/openresty-benchmarks.conf\n```\n\n## Launch tests\n\nI've created some pseudo-real world scenarios inside the `benchmarks` folder.\n\n```bash\n# for more info about syntax: https://openresty.gitbooks.io/programming-openresty/content/testing/test-modes.html\nexport TEST_NGINX_BENCHMARK='100000 40'\nprove -r ./benchmarks\n```\n\nBy default, only 1 nginx worker and 1 CPU core will be used to perform the benchmarks. To increase the worker limits,\nchange the `workers(1);` directive inside the `.t` files and re-run the benchmark.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanvouk%2Flua-resty-jwt-verification","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanvouk%2Flua-resty-jwt-verification","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanvouk%2Flua-resty-jwt-verification/lists"}