diff --git a/README.md b/README.md index 613ace6..718d7b5 100644 --- a/README.md +++ b/README.md @@ -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"; ``` @@ -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._ diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index fbd07ba..f5ca357 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -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; @@ -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, @@ -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; @@ -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"); @@ -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; @@ -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"); @@ -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); @@ -235,6 +243,7 @@ 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"); @@ -242,38 +251,43 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) } // 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); @@ -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; @@ -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 @@ -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 { @@ -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; @@ -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)) diff --git a/test.sh b/test.sh index adeb777..55230b4 100755 --- a/test.sh +++ b/test.sh @@ -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}";