├── config ├── LICENSE ├── README.md └── ngx_http_auth_jwt_module.c /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_auth_jwt_module 2 | 3 | ngx_module_type=HTTP 4 | ngx_module_name=ngx_http_auth_jwt_module 5 | ngx_module_srcs="$ngx_addon_dir/ngx_http_auth_jwt_module.c" 6 | ngx_module_libs="-ljansson -ljwt" 7 | 8 | . auto/module 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tiziano Puppi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngx_http_auth_jwt_module 2 | nginx c module to protect resources using jwt. 3 | 4 | This modules is heavenly inspired by the nginx original http_auth_jwt_module. Unfortunately this module is only available in the commercial subscription. This is a replacement that can be used by compiling it with the open source nginx. 5 | 6 | ## Dependencies 7 | 8 | This module depends on openssl, libjwt and jansson C libraries. 9 | 10 | ## Compile 11 | 12 | In order to compile, dowload source code for nginx and this repo. From the nginx folder, issue the following command. 13 | 14 | ``` 15 | ./configure --with-http_ssl_module --add-module=../ngx_http_auth_jwt_module 16 | ``` 17 | 18 | ## Usage 19 | 20 | There are few directives that can be used in the configuration file in order to activate this module. 21 | 22 | ### auth_jwt 23 | 24 | The usage of this directive is identical of the one on the original nginx PLUS http_auth_jwt_module: 25 | 26 | ``` 27 | Syntax: auth_jwt string [token=$variable] | off ; 28 | Default: auth_jwt off; 29 | Context: http, server, location 30 | ``` 31 | the optional token parameter takes a variable that contains the JSON Web Token. If not present the module expects the JSON Web Token to be passsed in the Authorization header as Bearer Token. Since token can be assigned to a variable, the JWT can be passed as a cookie or a query string. Example of usage: 32 | 33 | ``` 34 | auth_jwt "Reserved site" token=$cookie_myjwtcookie 35 | ``` 36 | 37 | The reserved value off disable the jwt protection. 38 | 39 | ### auth_jwt_key_file 40 | 41 | This directive is used to specify the file hosing the key. This must be a certificate in case JWT is encrypted using an asymmetric key encryption (RS256 for example) or the shared secret in case JWT is encrypted using a symmetric algorithm (HS256 for example). 42 | 43 | ``` 44 | Syntax: auth_jwt_key_file file; 45 | Default: - 46 | Context: http, server, location 47 | ``` 48 | 49 | ### auth_jwt_alg 50 | 51 | This directive is used to specify which algorithm the server expects to receive in the JWT. As suggested by [Auth0](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/) letting the creator ot the JWT to choose the encryption algorithm can leed to critical vulnerabilities. 52 | The specification of the algorithm is mandatory, and NONE is not accepted as a valid one. 53 | 54 | ``` 55 | Syntax: auth_jwt_alg HS256 | HS384 | HS512 | RS256 | RS384 | RS512 | ES256 | ES384 | ES512 56 | Default: - 57 | Context: http, server, location 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /ngx_http_auth_jwt_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define NGX_HTTP_AUTH_JWT_DISABLED -1 8 | #define NGX_HTTP_AUTH_JWT_ENABLED 1 9 | #define NGX_HTTP_AUTH_JWT_TOKEN 2 10 | 11 | static ngx_int_t 12 | ngx_http_auth_jwt_init(ngx_conf_t *cf); 13 | static void * 14 | ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf); 15 | static char * 16 | ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 17 | static char * 18 | ngx_http_auth_jwt_auth_jwt(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 19 | static ngx_int_t 20 | ngx_http_auth_jwt_handler(ngx_http_request_t *r); 21 | static ngx_int_t 22 | ngx_http_auth_jwt_set_realm(ngx_http_request_t *r, ngx_str_t *realm); 23 | static char * 24 | ngx_http_auth_jwt_key_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 25 | 26 | // Declare a struct to hold configurations 27 | typedef struct { 28 | ssize_t active; 29 | ngx_str_t realm; 30 | ngx_str_t key; 31 | ngx_uint_t jwt_algorithm; 32 | ngx_int_t variable_index; 33 | } ngx_http_auth_jwt_loc_conf_t; 34 | 35 | // enum of all algorithms 36 | static ngx_conf_enum_t ngx_http_jwt_algorithms[] = { 37 | { ngx_string("none"), JWT_ALG_NONE }, 38 | { ngx_string("HS256"), JWT_ALG_HS256 }, 39 | { ngx_string("HS384"), JWT_ALG_HS384 }, 40 | { ngx_string("HS512"), JWT_ALG_HS512 }, 41 | { ngx_string("RS256"), JWT_ALG_RS256 }, 42 | { ngx_string("RS384"), JWT_ALG_RS384 }, 43 | { ngx_string("RS512"), JWT_ALG_RS512 }, 44 | { ngx_string("ES256"), JWT_ALG_ES256 }, 45 | { ngx_string("ES384"), JWT_ALG_ES384 }, 46 | { ngx_string("ES512"), JWT_ALG_ES512 }, 47 | { ngx_null_string, 0 } 48 | }; 49 | 50 | 51 | // Definition for the directive 52 | static ngx_command_t ngx_http_auth_jwt_commands[] = { 53 | 54 | { ngx_string("auth_jwt"), 55 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, 56 | ngx_http_auth_jwt_auth_jwt, 57 | NGX_HTTP_LOC_CONF_OFFSET, 58 | 0, 59 | NULL }, 60 | 61 | { ngx_string("auth_jwt_key_file"), 62 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 63 | ngx_http_auth_jwt_key_file, 64 | NGX_HTTP_LOC_CONF_OFFSET, 65 | 0, 66 | NULL }, 67 | 68 | { ngx_string("auth_jwt_alg"), 69 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 70 | ngx_conf_set_enum_slot, 71 | NGX_HTTP_LOC_CONF_OFFSET, 72 | offsetof(ngx_http_auth_jwt_loc_conf_t, jwt_algorithm), 73 | &ngx_http_jwt_algorithms }, 74 | 75 | ngx_null_command 76 | }; 77 | 78 | // Context for the module 79 | static ngx_http_module_t ngx_http_auth_jwt_module_ctx = { 80 | NULL, /* preconfiguration */ 81 | ngx_http_auth_jwt_init, /* postconfiguration */ 82 | 83 | NULL, /* create main configuration */ 84 | NULL, /* init main configuration */ 85 | 86 | NULL, /* create server configuration */ 87 | NULL, /* merge server configuration */ 88 | 89 | ngx_http_auth_jwt_create_loc_conf, /* create location configuration */ 90 | ngx_http_auth_jwt_merge_loc_conf /* merge location configuration */ 91 | }; 92 | 93 | // Definition for the module 94 | ngx_module_t ngx_http_auth_jwt_module = { 95 | NGX_MODULE_V1, 96 | &ngx_http_auth_jwt_module_ctx, /* module context */ 97 | ngx_http_auth_jwt_commands, /* module directives */ 98 | NGX_HTTP_MODULE, /* module type */ 99 | NULL, /* init master */ 100 | NULL, /* init module */ 101 | NULL, /* init process */ 102 | NULL, /* init thread */ 103 | NULL, /* exit thread */ 104 | NULL, /* exit process */ 105 | NULL, /* exit master */ 106 | NGX_MODULE_V1_PADDING 107 | }; 108 | 109 | static ngx_int_t 110 | ngx_http_auth_jwt_init(ngx_conf_t *cf) 111 | { 112 | ngx_http_handler_pt *h; 113 | ngx_http_core_main_conf_t *cmcf; 114 | 115 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 116 | 117 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); 118 | if (h == NULL) { 119 | return NGX_ERROR; 120 | } 121 | 122 | *h = ngx_http_auth_jwt_handler; 123 | 124 | return NGX_OK; 125 | } 126 | 127 | static void * 128 | ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf) 129 | { 130 | ngx_http_auth_jwt_loc_conf_t * conf; 131 | 132 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_jwt_loc_conf_t)); 133 | if (conf == NULL) { 134 | return NULL; 135 | } 136 | 137 | // Set by pcalloc: 138 | // ssize_t active = 0; 139 | // ngx_str_t realm = {data = NULL; len = 0;} 140 | // ngx_str_t key = {data = NULL; len = 0;} 141 | // ngx_uint_t jwt_algorithm = 0; 142 | // ngx_int_t variable_index = 0; 143 | 144 | conf->variable_index = -1; 145 | conf->jwt_algorithm = NGX_CONF_UNSET_UINT; 146 | 147 | return conf; 148 | } 149 | 150 | static char * 151 | ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 152 | { 153 | ngx_http_auth_jwt_loc_conf_t *prev = parent; 154 | ngx_http_auth_jwt_loc_conf_t *conf = child; 155 | 156 | ngx_conf_merge_str_value(conf->realm, prev->realm, ""); 157 | ngx_conf_merge_str_value(conf->key, prev->key, ""); 158 | ngx_conf_merge_uint_value(conf->jwt_algorithm, prev->jwt_algorithm, JWT_ALG_NONE); 159 | 160 | if (conf->active == 0) { 161 | conf->active = prev->active; 162 | } 163 | 164 | if (conf->variable_index == -1) { 165 | conf->variable_index = prev->variable_index; 166 | } 167 | 168 | return NGX_CONF_OK; 169 | } 170 | 171 | static ngx_int_t 172 | ngx_http_auth_jwt_handler(ngx_http_request_t *r) 173 | { 174 | ngx_http_auth_jwt_loc_conf_t *alcf; 175 | ngx_http_variable_value_t *v; 176 | 177 | if (r->main->internal) { 178 | return NGX_DECLINED; 179 | } 180 | 181 | r->main->internal = 1; 182 | 183 | alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_jwt_module); 184 | 185 | if (alcf->active == 0 || alcf->active == NGX_HTTP_AUTH_JWT_DISABLED) { 186 | return NGX_DECLINED; 187 | } 188 | 189 | ngx_str_t jwt = ngx_string(""); 190 | 191 | // Retrieve authorization token from header 192 | if (alcf->active == NGX_HTTP_AUTH_JWT_ENABLED && r->headers_in.authorization) { 193 | ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header provided"); 194 | if (ngx_strncmp(r->headers_in.authorization->value.data, "Bearer ", 7) == 0) { 195 | jwt.data = r->headers_in.authorization->value.data + 7; 196 | jwt.len = r->headers_in.authorization->value.len - 7; 197 | } 198 | } 199 | 200 | // Retrieve autorization token from cookie 201 | if (alcf->active == NGX_HTTP_AUTH_JWT_TOKEN) { 202 | v = ngx_http_get_indexed_variable(r, alcf->variable_index); 203 | if (v->not_found) { 204 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, 205 | "cookie spedified in configuration was not provided for authentication"); 206 | 207 | return ngx_http_auth_jwt_set_realm(r, &alcf->realm); 208 | } 209 | jwt.data = v->data; 210 | jwt.len = v->len; 211 | } 212 | 213 | if (jwt.len == 0) { 214 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, 215 | "no jwt was provided for authentication"); 216 | 217 | return ngx_http_auth_jwt_set_realm(r, &alcf->realm); 218 | } 219 | 220 | if (alcf->key.len == 0) { 221 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, 222 | "no key to decode jwt was provided"); 223 | 224 | return ngx_http_auth_jwt_set_realm(r, &alcf->realm); 225 | } 226 | 227 | // Do no accept JWT_ALG_NONE as algorithm 228 | if (alcf->jwt_algorithm == JWT_ALG_NONE) { 229 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, 230 | "no valid algorithm to decode jwt was provided"); 231 | 232 | return ngx_http_auth_jwt_set_realm(r, &alcf->realm); 233 | } 234 | 235 | // the cookie data is not necessarily null terminated... we need a null terminated character pointer 236 | char *token_data = ngx_pcalloc(r->pool, jwt.len + 1); 237 | if (token_data == NULL) { 238 | return NGX_ERROR; 239 | } 240 | ngx_memcpy(token_data, jwt.data, jwt.len); 241 | 242 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 243 | "jwt_verify: authorization=%s", token_data); 244 | 245 | jwt_t* token; 246 | int err = jwt_decode(&token, token_data, alcf->key.data, alcf->key.len); 247 | if (err) { 248 | ngx_log_error(NGX_LOG_ERR, r->connection->log, errno, 249 | "jwt_verify: error on decode: %s", strerror(errno)); 250 | return ngx_http_auth_jwt_set_realm(r, &alcf->realm); 251 | } 252 | 253 | if (jwt_get_alg(token) != alcf->jwt_algorithm) { 254 | jwt_free(token); 255 | ngx_log_error(NGX_LOG_ERR, r->connection->log, errno, 256 | "jwt_verify: alg not accepted, rejected"); 257 | return ngx_http_auth_jwt_set_realm(r, &alcf->realm); 258 | } 259 | 260 | // validate the exp date of the JWT 261 | time_t exp = (time_t)jwt_get_grant_int(token, "exp"); 262 | time_t now = time(NULL); 263 | if (exp < now) 264 | { 265 | jwt_free(token); 266 | ngx_log_error(NGX_LOG_ERR, r->connection->log, errno, 267 | "jwt_verify: token expired, rejected"); 268 | return ngx_http_auth_jwt_set_realm(r, &alcf->realm); 269 | } 270 | 271 | return NGX_DECLINED; 272 | } 273 | 274 | static ngx_int_t 275 | ngx_http_auth_jwt_set_realm(ngx_http_request_t *r, ngx_str_t *realm) 276 | { 277 | size_t len; 278 | u_char *bearer, *p; 279 | 280 | r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers); 281 | if (r->headers_out.www_authenticate == NULL) { 282 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 283 | } 284 | 285 | len = sizeof("Bearer realm=\"\"") - 1 + realm->len; 286 | 287 | bearer = ngx_pnalloc(r->pool, len); 288 | if (bearer == NULL) { 289 | r->headers_out.www_authenticate->hash = 0; 290 | r->headers_out.www_authenticate = NULL; 291 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 292 | } 293 | 294 | p = ngx_cpymem(bearer, "Bearer realm=\"", sizeof("Bearer realm=\"") - 1); 295 | p = ngx_cpymem(p, realm->data, realm->len); 296 | *p = '"'; 297 | 298 | r->headers_out.www_authenticate->hash = 1; 299 | ngx_str_set(&r->headers_out.www_authenticate->key, "WWW-Authenticate"); 300 | r->headers_out.www_authenticate->value.data = bearer; 301 | r->headers_out.www_authenticate->value.len = len; 302 | 303 | return NGX_HTTP_UNAUTHORIZED; 304 | } 305 | 306 | // auth_jwt_key_file config directive callback 307 | static char * 308 | ngx_http_auth_jwt_key_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 309 | { 310 | ngx_http_auth_jwt_loc_conf_t * plcf = conf; 311 | ngx_str_t *args = cf->args->elts; 312 | char *key_file = (char *)args[1].data; 313 | // Determine file size (avoiding fseek) 314 | struct stat fstat; 315 | if (stat(key_file, &fstat) < 0) { 316 | ngx_conf_log_error(NGX_LOG_ERR, cf, errno, strerror(errno)); 317 | return NGX_CONF_ERROR; 318 | } 319 | FILE *fp = fopen(key_file, "rb"); 320 | if (fp == NULL) { 321 | ngx_conf_log_error(NGX_LOG_ERR, cf, errno, strerror(errno)); 322 | return NGX_CONF_ERROR; 323 | } 324 | plcf->key.len = fstat.st_size - 1; 325 | plcf->key.data = calloc(plcf->key.len, 1); 326 | if (fread(plcf->key.data, 1, plcf->key.len, fp) != plcf->key.len) { 327 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, 328 | "auth_jwt_key_file: unexpected end of file"); 329 | fclose(fp); 330 | return NGX_CONF_ERROR; 331 | } 332 | fclose(fp); 333 | return NGX_CONF_OK; 334 | } 335 | 336 | static char * 337 | ngx_http_auth_jwt_auth_jwt(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 338 | { 339 | ngx_http_auth_jwt_loc_conf_t * plcf = conf; 340 | 341 | ngx_str_t *value; 342 | 343 | value = cf->args->elts; 344 | 345 | if (ngx_strcmp(value[1].data, "off") == 0) { 346 | plcf->active = NGX_HTTP_AUTH_JWT_DISABLED; 347 | return NGX_CONF_OK; 348 | } 349 | 350 | // set realm 351 | plcf->active = NGX_HTTP_AUTH_JWT_ENABLED; 352 | plcf->realm.data = value[1].data; 353 | plcf->realm.len = value[1].len; 354 | 355 | if (cf->args->nelts > 2) { 356 | 357 | // check to see if second argument starts with "token=" 358 | if (value[2].len > sizeof("token=") - 1 359 | && ngx_strncmp(value[2].data, "token=", sizeof("token=") - 1) 360 | == 0) 361 | { 362 | 363 | value[2].data = value[2].data + sizeof("token=") - 1; 364 | value[2].len = value[2].len - (sizeof("token=") - 1); 365 | 366 | // check if second part is a variable 367 | if (value[2].data[0] != '$') { 368 | 369 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 370 | "invalid variable name \"%V\"", &value[2]); 371 | return NGX_CONF_ERROR; 372 | } 373 | 374 | plcf->active = NGX_HTTP_AUTH_JWT_TOKEN; 375 | value[2].data++; 376 | value[2].len--; 377 | 378 | plcf->variable_index = ngx_http_get_variable_index(cf, &value[2]); 379 | if (plcf->variable_index == NGX_ERROR) { 380 | 381 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 382 | "invalid variable name \"%V\"", &value[2]); 383 | return NGX_CONF_ERROR; 384 | } 385 | 386 | 387 | } else { 388 | 389 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 390 | "invalid optional token argument \"%V\"", &value[2]); 391 | return NGX_CONF_ERROR; 392 | } 393 | 394 | } 395 | 396 | return NGX_CONF_OK; 397 | } 398 | --------------------------------------------------------------------------------