The ox_jwt repository provides some simple tools for working with JSON Web
Tokens (JWTs) as well as illustrating some use cases with nginx and python.
For example, the nginx sub-directory contains a fully working
minimal example of how you can setup NGINX to only allow access to
your locations if the client has a JWT in the URL, header, or browser
cookie.
This is useful because:
- You can easily protect access to your internal or external servers.
- For example, requiring a JWT will block hackers trying to brute force username/password combinations.
- Checking the JWT in NGINX prevents hackers from exploiting vulnerabilities in your application server.
- Since NGINX is a widely used open source web server, this approach to security is easy to integrate with most systems.
- All of the encryption/decryption used is open source, verifiable, and modifiable by you.
The overview slides provide a detailed presentation of what JWTs are and how to use them in python.
You can use JWTs in python with or without nginx. For example, you could have nginx do validation of the JWT and then provide the decoded JWT as a header to further python code. Alternatively, you can do JWT encoding and decoding purely in python.
If you pip install ox_jwt, you can then run the command
test_ox_jwt to do a simple local test of encoding JWTs and decoding
them with a flask server. See the python files in src/ox_jwt/ to see
how the python example works.
For a simple test of the nginx system, you can do something like
make testat the command line to do the following:
- Install the npm dependencies listed in nginx/ojwt/package.json
using
npm install(you will need to have a reasonable version ofnpmalready installed). - Create private/public/test keys in
nginx/ojwt/*.pem - Use the keys to create a test JWT in
ojwt/prod_test_jwt.txt.- This JWT is keyed to the host
example.com.
- This JWT is keyed to the host
- Build the single file javascript bundle in
nginx/ojwt/ojwt.js.- This is the only file that you need to put onto your NGINX web server. It contains all the encryption/decryption code as well as your public key (but not your private key).
- Build and start a simple NGINX docker container using nginx/Dockerfile.
- Uses
curlto verify that you can only access the protected location in your NGINX server if you have the appropriate JWT.
For a test of the python implementation, you can do
pip install ox_jwtand then run the test_ox_jwt command.
Alternatively, you can clone the GitHub repository via something like
git clone https://github.com/aocks/ox_jwt.gitand then build a virtual env and install dependencies via
cd ox_jwt
python3 -m venv venv_ox_jwt
source venv_ox_jwt/bin/activate
pip install -e .and then run the tests/demo via:
py.test src/ox_jwt- SSL client certificates
- but SSL client certificates are difficult to setup and maintain without providing any additional security beyond JWTs.
- NGINX PLUS commercial web server includes JWT support
- but the licensing for NGINX PLUS is expensive
- and you have more control with
ox_jwtsince you can see and modify all of the JWT validation code yourself
- Using a library in your application server such as:
- Flask-JWT or flask-jwt-extended for the Flask python web server
- jwt-simple for javascript (used by this project as well)
- pyjwt for general python JWT tools
Not that I am aware of. Most other approaches to JWT validation in your APPLICATION server instead of your NGINX server or proxy or require a commercial (i.e., non-open-source) server.
Plenty of applications do JWT validation and decoding themselves as shown in the python examples. That is a fine and useful thing to do and can also be combined with validating the JWT in NGINX as well.
A few reasons why you might want to do JWT validation in the web server instead of or in addition to the application include:
- Reduces load on the application server.
- Most external systems will be subject to constant attacks and probes by hackers. Using JWT validation where possible prevents these attacks from even getting to the application server which can be useful.
- Simplify security audits.
- Application servers can be complicated which makes reasoning about security in an audit more difficult. By putting a relatively simple JWT validation layer at your NGINX proxy, you can make it easier for your security professionals to audit, maintain, and verify your security.
- For example, if you have many different application servers written in different languages with different technology stacks, it can be difficult for security professionals to review and manage all of them. By providing JWT validation in the single entry point (i.e., NGINX) to all your applications, you have a potentially cleaner, more modular system.
- Simplify logging. Monitoring JWT validation failures as well as successful access can be simpler if that is done in a single place.
See the NGINX configuration file in nginx/conf.d/example.conf. Basically you do the following in your NGINX configuration file:
- Add a line like
js_import conf.d/ojwt.js;near the top of your config.- This tells where to find the
ojwt.jssingle file javascript bundle which you build viacd nginx/ojwt && make ojwt.js. - The
ojwt.jsincludes your public key.
- This tells where to find the
- Add a line like
js_set $decoded_ojwt auth_tools.decode_jwt;near the top of your config.- This tells NGINX to use the
auth_tools.decode_jwtfunction fromojwt.jsto try to decode JWTs from the URL or header or cookie of incoming requests and put the result into the$decoded_ojwtNGINX variable.
- This tells NGINX to use the
- Put a block like the following in your NGINX config to return a 401 error if the JWT is not valid:
location /protected {
if ($decoded_ojwt ~ "^fail.*") {
return 401 $decoded_ojwt;
}
}
Once you have the above, all of your protected locations will require valid JWTs but users can still access any locations you choose not to protect as usual.
You can create JWTs as usual using your private RSA key. As a
convenience, ox_jwt provides a simple command line tool to generate
JWTs. To access it, simply do something like the following:
cd /path/to/nginx/ojwt/
make private_key.pem # only need to do this once; or provide your own
node otools.js encode -k private_key.pem \
-m "my example msg" -h example.com \
--exp `date +%s --date=tomorrow`
# You can replace the above parameters as you like or omit them.When you want to give someone access to a server or area that you have
protected, you simply give them a JWT created using your
private_key.pem (keep this file secure!). See the above discussion
for How can I create a JWT for a user.
The user then provides this JWT in the URL query parameters via
something like ?jwt=YOUR_JWT. NGINX will contain the PUBLIC key but
not the PRIVATE key. So when the user attempts to gain access, NGINX
will verify that the JWT was created by the corresponding private key.
Note that because we use public key cryptography, you do not need to
store any secret information on the NGINX server. You simply store the
PUBLIC which cannot be used to create new JWTs. Thus if your NGINX
server is compromised or non-privileged people have access to the
ojwt.js file you deploy, that is not a problem.
The decoded payload will be put into the variable requested. For example, if you use a configuration like nginx/conf.d/example.conf, you can do the following:
- Build and start the example dockerized NGINX server via:
cd ox_jwt/nginx && run_ojwt_nginx
- Use curl to check the token and see the response headers via:
make curl_verbose_token_check
- The payload of the JWT used (
ojwt/prod_test_jwt.txtby default) will be shown in theX-decoded-ojwtresponse header in JSON format. - Stop the docker container via:
make stop_ojwt_nginx
You can view the decoded JWT payload and use it either in NGINX or in your application server.
Please create a new issue on GitHub.