Skip to content

Feature/support token in query string #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ By default, the`Authorization` header is used to provide a JWT for validation. H
```nginx
auth_jwt_location HEADER=auth-token; # get the JWT from the "auth-token" header
auth_jwt_location COOKIE=auth-token; # get the JWT from the "auth-token" cookie
auth_jwt_location QUERY_STRING=jwt-token; # get the JWT from the "jwt-token" query-string
```

Note: If you use the QUERY_STRING location, the given key is stripped from the outgoing request, if the request is to be forwarded.

## `sub` Validation

Optionally, the module can validate that a `sub` claim (e.g. the user's id) exists in the JWT. You may enable this feature as follows:
Expand Down
2 changes: 1 addition & 1 deletion config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ngx_module_type=HTTP
ngx_addon_name=ngx_http_auth_jwt_module
ngx_module_name=$ngx_addon_name
ngx_module_srcs="${ngx_addon_dir}/src/arrays.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_binary_converters.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_header_processing.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_string.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_module.c"
ngx_module_srcs="${ngx_addon_dir}/src/arrays.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_args_processing.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_binary_converters.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_header_processing.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_string.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_module.c"
ngx_module_libs="-ljansson -ljwt -lm"

. auto/module
73 changes: 73 additions & 0 deletions src/ngx_http_auth_jwt_args_processing.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "ngx_http_auth_jwt_args_processing.h"

u_char *create_args_without_token(
ngx_pool_t *pool,
ngx_str_t *args,
size_t token_key_start,
size_t token_end,
size_t *write_args_len
) {
/* Creates a new version of args without token present.
Writes length of new args to *write_args_len
*/
*write_args_len = args->len - token_end + token_key_start;

u_char *args_ptr = ngx_palloc(pool, *write_args_len);

if (args_ptr == NULL) return NULL;

if (token_key_start > 0) {
ngx_memcpy(args_ptr, args->data, token_key_start);
}
if (token_end < (args->len - 1)) {
ngx_memcpy(
args_ptr + token_key_start,
args->data + token_end,
*write_args_len - token_key_start
);
}

return args_ptr;
}

bool search_token_from_args(
const ngx_str_t *jwt_location,
const ngx_str_t *args,
size_t *write_to_token_key_start,
size_t *write_to_token_value_start,
size_t *write_to_token_end
) {
/* Tries to extract token from query string. Returns true if found, false otherwise.

Searches for the string contained in *jwt_location in *args. If it finds the token
in question it writes the location of the start of key to *write_to_token_key_start,
start of token itself to *write_to_token_value_start and end of token to *write_to_token_end.
*/
size_t i = 0, j = 0;
size_t max_i = args->len > jwt_location->len ? args->len - jwt_location->len : 0;

while (i < max_i) {
j = 0;
if (i == 0 || *(args->data + i - 1) == '&') {
while (j < jwt_location->len && *(args->data + i + j) == *(jwt_location->data + j)) {
if (j == (jwt_location->len - 1)) {
*write_to_token_key_start = i;
i++;
if (i >= max_i || *(args->data + i + j) != '=') {
// key doesn't match
break;
}
*write_to_token_value_start = i + j + 1;
while (i < args->len && *(args->data + i) != '&') {
i++;
}
*write_to_token_end = i;
return true;
}
j++;
}
}
i++;
}
return false;
}
23 changes: 23 additions & 0 deletions src/ngx_http_auth_jwt_args_processing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef _NGX_HTTP_AUTH_JWT_ARGS_PROCESSING_H
#define _NGX_HTTP_AUTH_JWT_ARGS_PROCESSING_H

#include <ngx_core.h>
#include <stdbool.h>

u_char *create_args_without_token(
ngx_pool_t *pool,
ngx_str_t *args,
size_t token_key_start,
size_t token_end,
size_t *write_mutated_args_len
);

bool search_token_from_args(
const ngx_str_t *jwt_location,
const ngx_str_t *args,
size_t *write_to_token_key_start,
size_t *write_to_token_value_start,
size_t *write_to_token_end
);

#endif /* _NGX_HTTP_AUTH_JWT_ARGS_PROCESSING_H */
51 changes: 51 additions & 0 deletions src/ngx_http_auth_jwt_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <jansson.h>

#include "arrays.h"
#include "ngx_http_auth_jwt_args_processing.h"
#include "ngx_http_auth_jwt_header_processing.h"
#include "ngx_http_auth_jwt_binary_converters.h"
#include "ngx_http_auth_jwt_string.h"
Expand Down Expand Up @@ -613,6 +614,7 @@ static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location)
{
static const char *HEADER_PREFIX = "HEADER=";
static const char *COOKIE_PREFIX = "COOKIE=";
static const char QUERY_STRING_PREFIX[] = "QUERY_STRING=";
char *jwtPtr = NULL;

ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "jwt_location.len %d", jwt_location.len);
Expand Down Expand Up @@ -670,6 +672,55 @@ static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location)
jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtCookieVal);
}
}
else if (jwt_location.len > sizeof(QUERY_STRING_PREFIX) && ngx_strncmp(jwt_location.data, QUERY_STRING_PREFIX, sizeof(QUERY_STRING_PREFIX) - 1) == 0) {
jwt_location.data += sizeof(QUERY_STRING_PREFIX) - 1;
jwt_location.len -= sizeof(QUERY_STRING_PREFIX) - 1;

size_t token_key_start = 0, token_value_start = 0, token_end = 0;

bool found_token = search_token_from_args(
&jwt_location,
&r->args,
&token_key_start,
&token_value_start,
&token_end
);

if (!found_token) return NULL;

int token_len = token_end - token_value_start;

jwtPtr = ngx_palloc(r->pool, token_len + 1);
if (jwtPtr != NULL) {
ngx_memcpy(jwtPtr, r->args.data + token_value_start, token_len);
*(jwtPtr + token_len) = '\0';
}

// strip first or last & from args
if (token_key_start > 0) {
token_key_start--;
} else if (token_end < (r->args.len - 1)) {
token_end++;
}

size_t mutated_args_len = 0;

// Strip key from args
u_char *args_ptr = create_args_without_token(
r->pool,
&r->args,
token_key_start,
token_end,
&mutated_args_len
);

if (args_ptr == NULL) return jwtPtr;

int free_ok = ngx_pfree(r->pool, r->args.data);
if (free_ok == NGX_OK) {
r->args.data = args_ptr;
r->args.len = mutated_args_len;
}
}
return jwtPtr;
}
9 changes: 9 additions & 0 deletions test/etc/nginx/conf.d/test.conf
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ BwIDAQAB
try_files index.html =404;
}

location /secure/auth-query-string/hs256 {
auth_jwt_enabled on;
auth_jwt_location QUERY_STRING=jwt-token;
auth_jwt_algorithm HS256;

alias /usr/share/nginx/html/;
try_files index.html =404;
}

location /secure/extract-claim/request/sub {
auth_jwt_enabled on;
auth_jwt_redirect off;
Expand Down
36 changes: 36 additions & 0 deletions test/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,42 @@ main() {
-p '/secure/custom-header/hs256/' \
-c '200' \
-x '--header "Auth-Token: Bearer ${JWT_HS256_VALID}"'

run_test -n 'when auth enabled with HS256 algorithm and valid JWT as query-string jwt-token, returns 200' \
-p '/secure/auth-query-string/hs256/?jwt-token=${JWT_HS256_VALID}' \
-c '200'

run_test -n 'when auth enabled with HS256 algorithm and valid JWT as query-string something-jwt-token, returns 401' \
-p '/secure/auth-query-string/hs256/?something-jwt-token=${JWT_HS256_VALID}' \
-c '401'

run_test -n 'when auth enabled with HS256 algorithm and non-valid JWT as query-string jwt-token, returns 401' \
-p '/secure/auth-query-string/hs256/?jwt-token=ABBAKEY' \
-c '401'

run_test -n 'when auth enabled with HS256 algorithm and no query-string present, returns 401' \
-p '/secure/auth-query-string/hs256/' \
-c '401'

run_test -n 'when auth enabled with HS256 algorithm and just random query-string present, returns 401' \
-p '/secure/auth-query-string/hs256/?key1=abba' \
-c '401'

run_test -n 'when auth enabled with HS256 algorithm and valid JWT as query-string jwt-token after first query-string, returns 200' \
-p '/secure/auth-query-string/hs256/?key1=abba\&jwt-token=${JWT_HS256_VALID}' \
-c '200'

run_test -n 'when auth enabled with HS256 algorithm and valid JWT as query-string jwt-token, second query-string after, returns 200' \
-p '/secure/auth-query-string/hs256/?jwt-token=${JWT_HS256_VALID}\&key1=abba' \
-c '200'

run_test -n 'when auth enabled with HS256 algorithm and valid JWT as query-string jwt-token in middle, returns 200' \
-p '/secure/auth-query-string/hs256/?key1=abba\&jwt-token=${JWT_HS256_VALID}\&key2=abba' \
-c '200'

run_test -n 'when auth enabled with HS256 algorithm and valid JWT as query-string jwt-token with overlapping query-string first, returns 200' \
-p '/secure/auth-query-string/hs256/?jwt-token2=abba\&jwt-token=${JWT_HS256_VALID}' \
-c '200'

run_test -n 'extracts single claim to request variable' \
-p '/secure/extract-claim/request/sub' \
Expand Down