Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ which can be specified in on the `main` `server` or `location` level.
auth_jwt_key "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; # see docs for format based on algorithm
auth_jwt_loginurl "https://yourdomain.com/loginpage";
auth_jwt_enabled on;
auth_jwt_algorithm HS256; # or RS256
auth_jwt_algorithm HS256; # or RS256
auth_jwt_extract_sub on; # or off
auth_jwt_validate_email on; # or off
auth_jwt_use_keyfile off; # or on
auth_jwt_use_keyfile off; # or on
auth_jwt_keyfile_path "/app/pub_key";
```

Expand Down Expand Up @@ -87,9 +88,17 @@ auth_jwt_validation_type COOKIE=rampartjwt;
By default the authorization header is used to provide a JWT for validation.
However, you may use the `auth_jwt_validation_type` configuration to specify the name of a cookie that provides the JWT.

```
auth_jwt_extract_sub
```
By default, the module will attempt to extract the `sub` claim (e.g. the user's id) from the JWT. If successful, the
value will be set in the `x-userid` HTTP header. An error will be logged if this option is enabled and the JWT does not
contain the `sub` claim.

```
auth_jwt_validate_email off;
```
By default, the module will attempt to validate the email address field of the JWT, then set the x-email header of the
session, and will log an error if it isn't found. To disable this behavior, for instance if you are using a different
user identifier property such as 'sub', set `auth_jwt_validate_email` to the value `off`.
user identifier property such as `sub`, set `auth_jwt_validate_email` to the value `off`. _Note that this flag may be
renamed to `auth_jwt_extract_email` in a future release._
55 changes: 32 additions & 23 deletions src/ngx_http_auth_jwt_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ typedef struct {
ngx_flag_t auth_jwt_redirect;
ngx_str_t auth_jwt_validation_type;
ngx_str_t auth_jwt_algorithm;
ngx_flag_t auth_jwt_extract_sub;
ngx_flag_t auth_jwt_validate_email;
ngx_str_t auth_jwt_keyfile_path;
ngx_flag_t auth_jwt_use_keyfile;
Expand Down Expand Up @@ -84,6 +85,13 @@ static ngx_command_t ngx_http_auth_jwt_commands[] = {
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_algorithm),
NULL },

{ ngx_string("auth_jwt_extract_sub"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_extract_sub),
NULL },

{ ngx_string("auth_jwt_validate_email"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
Expand Down Expand Up @@ -152,10 +160,6 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
jwt_t *jwt = NULL;
int jwtParseReturnCode;
jwt_alg_t alg;
const char* sub;
const char* email;
ngx_str_t sub_t;
ngx_str_t email_t;
time_t exp;
time_t now;
ngx_str_t auth_jwt_algorithm;
Expand All @@ -175,6 +179,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
}

jwtCookieValChrPtr = getJwt(r, jwtcf->auth_jwt_validation_type);

if (jwtCookieValChrPtr == NULL)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a jwt");
Expand All @@ -184,6 +189,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
// convert key from hex to binary, if a symmetric key

auth_jwt_algorithm = jwtcf->auth_jwt_algorithm;

if (auth_jwt_algorithm.len == 0 || (auth_jwt_algorithm.len == sizeof("HS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "HS256", sizeof("HS256") - 1)==0))
{
keylen = jwtcf->auth_jwt_key.len / 2;
Expand Down Expand Up @@ -218,6 +224,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)

// validate the jwt
jwtParseReturnCode = jwt_decode(&jwt, jwtCookieValChrPtr, keyBinary, keylen);

if (jwtParseReturnCode != 0)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse jwt");
Expand All @@ -226,6 +233,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)

// validate the algorithm
alg = jwt_get_alg(jwt);

if (alg != JWT_ALG_HS256 && alg != JWT_ALG_RS256)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm in jwt %d", alg);
Expand All @@ -235,45 +243,51 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
// validate the exp date of the JWT
exp = (time_t)jwt_get_grant_int(jwt, "exp");
now = time(NULL);

if (exp < now)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt has expired");
goto redirect;
}

// extract the userid
sub = jwt_get_grant(jwt, "sub");
if (sub == NULL)
if (jwtcf->auth_jwt_extract_sub == 1)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain a subject");
}
else
{
sub_t = ngx_char_ptr_to_str_t(r->pool, (char *)sub);
set_custom_header_in_headers_out(r, &useridHeaderName, &sub_t);
const char* sub = jwt_get_grant(jwt, "sub");

if (sub == NULL)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain a subject");
}
else
{
ngx_str_t sub_t = ngx_char_ptr_to_str_t(r->pool, (char *)sub);

set_custom_header_in_headers_out(r, &useridHeaderName, &sub_t);
}
}

if (jwtcf->auth_jwt_validate_email == 1)
{
email = jwt_get_grant(jwt, "emailAddress");
const char* email = jwt_get_grant(jwt, "emailAddress");

if (email == NULL)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain an email address");
}
else
{
email_t = ngx_char_ptr_to_str_t(r->pool, (char *)email);
ngx_str_t email_t = ngx_char_ptr_to_str_t(r->pool, (char *)email);

set_custom_header_in_headers_out(r, &emailHeaderName, &email_t);
}
}

jwt_free(jwt);


return NGX_OK;

redirect:

if (jwt)
{
jwt_free(jwt);
Expand Down Expand Up @@ -303,7 +317,6 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
uintptr_t escaped_len;

loginlen = jwtcf->auth_jwt_loginurl.len;

scheme = (r->connection->ssl) ? "https" : "http";
server = r->headers_in.server;

Expand All @@ -318,15 +331,11 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
uri.data = ngx_palloc(r->pool, request_uri_var->len);
uri.len = request_uri_var->len;
ngx_memcpy(uri.data, request_uri_var->data, request_uri_var->len);

// ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "found uri with querystring %s", ngx_str_t_to_char_ptr(r->pool, uri));
}
else
{
// fallback to the querystring without params
uri = r->uri;

// ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "fallback to querystring without params");
}

// escape the URI
Expand All @@ -350,8 +359,6 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
ngx_memcpy(return_url+return_url_idx, uri_escaped.data, uri_escaped.len);
return_url_idx += uri_escaped.len;
r->headers_out.location->value.data = (u_char *)return_url;

// ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "return_url: %s", ngx_str_t_to_char_ptr(r->pool, r->headers_out.location->value));
}
else
{
Expand Down Expand Up @@ -403,6 +410,7 @@ ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf)
// set the flag to unset
conf->auth_jwt_enabled = (ngx_flag_t) -1;
conf->auth_jwt_redirect = (ngx_flag_t) -1;
conf->auth_jwt_extract_sub = (ngx_flag_t) -1;
conf->auth_jwt_validate_email = (ngx_flag_t) -1;
conf->auth_jwt_use_keyfile = (ngx_flag_t) -1;

Expand Down Expand Up @@ -453,6 +461,7 @@ ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_str_value(conf->auth_jwt_validation_type, prev->auth_jwt_validation_type, "");
ngx_conf_merge_str_value(conf->auth_jwt_algorithm, prev->auth_jwt_algorithm, "HS256");
ngx_conf_merge_str_value(conf->auth_jwt_keyfile_path, prev->auth_jwt_keyfile_path, "");
ngx_conf_merge_off_value(conf->auth_jwt_extract_sub, prev->auth_jwt_extract_sub, 1);
ngx_conf_merge_off_value(conf->auth_jwt_validate_email, prev->auth_jwt_validate_email, 1);

if (conf->auth_jwt_enabled == ((ngx_flag_t) -1))
Expand Down
5 changes: 2 additions & 3 deletions test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ test_jwt () {
local extra=$4

cmd="curl -X GET -o /dev/null --silent --head --write-out '%{http_code}' http://nginx:8000$path -H 'cache-control: no-cache' $extra"


test=$( eval ${cmd} )
if [ "$test" -eq "$expect" ];then

if [ "$test" -eq "$expect" ]; then
echo -e "${GREEN}${name}: passed (${test})${NONE}";
else
echo -e "${RED}${name}: failed (${test})${NONE}";
Expand Down