lua-resty-jwt

repository·master·Indexed 19 days ago

https://github.com/skylothar/lua-resty-jwt

A Lua library for handling JSON Web Tokens (JWT) and JSON Web Encryption (JWE) within the ngx_lua/OpenResty environment. It provides capabilities for signing, verification, and claim validation, including support for Key ID (kid) lookups via Redis and custom validator functions through claim_spec or the resty.jwt-validators helper library.

Tokens
2.2K
Snippets
11
Records
11
Agent score
69%

What's inside lua-resty-jwt

  1. How JWT claim validation works

    master

    Verification can include custom logic via claim_spec. A claim_spec is a Lua table where keys match payload keys, and values are validator functions.

    Validator Signature: function(val, claim, jwt_json)

    • val: The value of the claim being tested (or nil).
    • claim: The name of the claim.
    • jwt_json: The JSON-serialized representation of the object.

    Rules:

    • Return true or false for success/failure.
    • A validator may raise an error; if it does, validation fails and the error is stored in the reason field of the resulting object.
    • If a validator returns nil, it is treated as a success (assuming it would have raised an error if it failed).
    • Use the special claim __jwt to access a deep clone of the entire parsed JWT object as the val parameter.
    local claim_spec = {
        sub = function(val) return string.match("^[a-z]+$", val) end,
        __jwt = function(val, claim, jwt_json)
            if val.payload.foo == nil then
                error("Missing foo claim")
            end
        end
    }
    
    local jwt_obj = jwt:verify(key, token, claim_spec)
  2. Configure nginx.conf for lua-resty-jwt

    master

    To use the library, you must add the library's path to the lua_package_path directive in your nginx.conf so that require "resty.jwt" can locate the files.

    # nginx.conf
    http {
        lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;";
        ...
    }
  3. Implement JWT authentication using query parameters or cookies

    master

    You can protect Nginx locations by using an access_by_lua_file directive that points to a guard script. This pattern typically involves setting a $jwt_secret variable in the Nginx configuration which the Lua script then uses to validate incoming JWTs found in either the query string or cookies.

    To implement this, define your secret in the location block and call your guard script via access_by_lua_file.

    location / {
        access_log off;
        default_type text/plain;
    
        set $jwt_secret "your-own-jwt-secret";
        access_by_lua_file /etc/nginx/lua/guard.lua;
    
        echo "i am protected by jwt guard";
    }
  4. Install lua-resty-jwt

    master

    You can install the library using OPM, LuaRocks, or by downloading the source directly.

    Dependencies: This library requires an Nginx build with OpenSSL, the ngx_lua module, LuaJIT 2.0, lua-resty-hmac (specifically the one from jkeys089, not the LuaRocks version), and lua-resty-string).

    # Using OPM
    opm get SkyLothar/lua-resty-jwt
    
    # Using LuaRocks
    luarocks install lua-resty-jwt
  5. Implement JWT authentication with Key ID (kid) and Redis key storage

    master

    For more advanced setups, you can use a kid (Key ID) to identify which key to use for verification and store those keys in a Redis instance. This allows for dynamic key rotation without restarting Nginx.

    In your Nginx configuration, you must define the Redis connection parameters (host and port) before calling the Lua script that handles the Redis-backed JWT verification.

    location / {
        set $redhost "127.0.0.1";
        set $redport 6379;
        # set $reddb 1;
        # set $redauth "your-redis-pass";
        access_by_lua_file /etc/nginx/lua/redjwt.lua;
    
        echo "i am protected jwt guard";
    }
  6. Use legacy validation options for timeframe checks

    master

    Instead of complex claim_spec tables, you can pass a validation_options table to jwt:load or jwt:verify_jwt_obj.

    Warning: You cannot mix legacy options with other claim_spec validators. If you need both, pass them as separate arguments to the function.

    Available Options:

    • lifetime_grace_period: (Number) Leeway in seconds for nbf and exp claims. Automatically requires nbf or exp to exist.
    • require_nbf_claim: (Boolean) If true, the nbf claim must be present.
    • require_exp_claim: (Boolean) If true, the exp claim must be present.
    • valid_issuers: (Array of strings) A whitelist of allowed iss values.
    local jwt_obj = jwt:verify(key, jwt_token, {
        lifetime_grace_period = 120,
        require_exp_claim = true,
        valid_issuers = { "my-trusted-issuer" }
    })
  7. Verify a JWT token

    master

    Use jwt:verify(key, jwt_token [, claim_spec [, ...]]) to verify a token.

    Key parameter types:

    • A pre-shared key (string).
    • A function that takes a single parameter (the kid value from the header) and returns the pre-shared key (string) or nil if the lookup fails.
    local jwt = require "resty.jwt"
    
    -- Using a string key
    local jwt_obj = jwt:verify("secret", "token_string")
    
    -- Using a function for KID lookup
    local jwt_obj = jwt:verify(function(kid) 
        if kid == "key-1" then return "secret-1" end
        return nil
    end, "token_string")
  8. Load and verify a JWT object

    master

    If you have a parsed JWT object (or want to handle the kid lookup and verification in one step), use these methods:

    1. jwt:load_jwt(jwt_token): Parses the token into a jwt_obj table.
    2. jwt:verify_jwt_obj(key, jwt_obj [, claim_spec [, ...]]): Verifies an existing object.

    Combining these allows you to load a JWT, check for a kid, and then verify it with the correct key automatically.

    local jwt = require "resty.jwt"
    
    -- Combined pattern: load then verify
    local jwt_obj = jwt:load_jwt(jwt_token)
    local verified_obj = jwt:verify_jwt_obj(key, jwt_obj)
  9. Sign a JWT token

    master

    Use the jwt:sign(key, table_of_jwt) method to generate a JWT token from a Lua table. The alg argument in the header determines the hashing algorithm (e.g., HS256, HS512, RS256).

    local jwt = require "resty.jwt"
    
    local jwt_token = jwt:sign(
        "your-secret-key",
        {
            header={typ="JWT", alg="HS256"},
            payload={foo="bar"}
        }
    )
  10. Sign a JWE (JSON Web Encryption)

    master

    You can sign a JWE using the same jwt:sign method, but with specific header arguments:

    • alg: The hashing algorithm for encrypting the key (e.g., dir).
    • enc: The hashing algorithm for encrypting the payload (e.g., A128CBC-HS256, A256CBC-HS512).
    local jwt_token = jwt:sign(
        "key",
        {
            header={typ="JWE", alg="dir", enc="A128CBC-HS256"},
            payload={foo="bar"}
        }
    )
  11. Use JWT validators from resty.jwt-validators

    master

    The resty.jwt-validators library provides a suite of helper functions for creating claim_spec validators.

    Common Validators:

    • validators.chain(...): Chains multiple validators.
    • validators.required(fn): Ensures a value exists and passes fn.
    • validators.equals(val) / validators.equals_any_of(vals): Checks equality.
    • validators.matches(pattern): Regex match.
    • validators.is_not_before() / validators.is_not_expired(): Timeframe checks.
    • validators.require_one_of(keys): Ensures at least one of the keys exists.

    Note: Functions suffixed with opt_ (e.g., opt_matches) return true if the key is missing from the payload, whereas the non-opt versions return false if the key is missing.

    local validators = require "resty.jwt-validators"
    
    local claim_spec = {
        sub = validators.opt_matches("^[a-z]+$"),
        iss = validators.equals_any_of({ "first", "second" }),
        __jwt = validators.require_one_of({ "foo", "bar" })
    }