This is a demo of an nginx
implementation with OpenID Connect (keycloak
) with a Rest API (elasticsearch
)
Download Keycloak and run it locally
$ cd keycloak-12.0.2
$ ./bin/standalone.sh
-
Create a
keycloak
realm, name ittest-nginx
(or anything you want) -
Create a user in your realm, make sure to check
enbled
and set a non-temporary password in thecredentials
tab -
Create a
.env
file and set your environment variables
REALM=test-nginx
Download Elasticsearch and run it locally:
$ cd elasticsearch-7.9.3
$ ./bin/elasticsearch
I'm using OpenResty - a full-fledged web platform that integrates an enhanced open-sourced version of Nginx
- OpenID Connect integration is supported only in NGINX-PLUS, but it requires a license
The lua-resty-openidc library is used to authenticate requests against the OpenID Connect server (keycloak
)
Authentication is handled at the proxy level under a lua
code block in nginx.conf
access_by_lua_block {
local opts = {
discovery = string.format("http://127.0.0.1/auth/realms/%s/.well-known/openid-configuration", os.getenv("REALM")),
token_endpoint_auth_method = "client_secret_post",
}
local json, err, access_token = require("resty.openidc").bearer_jwt_verify(opts)
if err then
ngx.status = 401
ngx.say(string.format([[{"message": "%s"}]], string.gsub(err, '"', '\\"')))
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
}
The JWT token is validated through the discovery endpoint: .well-known/openid-configuration
- Each incoming request will hit the
keycloak
token introspection endpoint to validate the token - You can do offline token validation by adding a
public_key
in thelua
code block: LINK
- Build and run the docker image
$ docker-compose up --build
# Building web-server
# Step 1/3 : FROM openresty/openresty:latest
# .
# .
# .
# Step 3/3 : COPY ./nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
# ---> Using cache
# ---> 0e304c9e93bb
# Successfully built 0e304c9e93bb
# Successfully tagged nginx-openid-demo_web-server:latest
# Recreating 3a87ad1d5f62_nginx-openid-demo_web-server_1 ... done
# Attaching to nginx-openid-demo_web-server_1
- Retrieve the
access_token
fromkeycloak
$ export USERNAME=<username>
$ export PASSWORD=<password>
$ export REALM=test-nginx
$ export CLIENT_ID=nginx
$ export CLIENT_SECRET=client_secret_from_keycloak
$ export ACCESS_TOKEN=`curl -s \
-d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET" \
-d "username=$USERNAME" -d "password=$PASSWORD" \
-d "grant_type=password" \
"http://localhost/auth/realms/$REALM/protocol/openid-connect/token" | jq -r '.access_token'`
$ echo $ACCESS_TOKEN
# eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiw...Ji9KMRrshGR9psIAMk6upxb3g
- Attach the
access_token
to theAuthorization
header in your request toelasticsearch
$ curl -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost/es/
# {
# "name" : "LMC-063631",
# "cluster_name" : "elasticsearch",
# "cluster_uuid" : "UeeuGwi3ThKFmruh9LO53A",
# "version" : {
# "number" : "7.9.3",
# "build_flavor" : "default",
# "build_type" : "tar",
# "build_hash" : "c4138e51121ef06a6404866cddc601906fe5c868",
# "build_date" : "2020-10-16T10:36:16.141335Z",
# "build_snapshot" : false,
# "lucene_version" : "8.6.2",
# "minimum_wire_compatibility_version" : "6.8.0",
# "minimum_index_compatibility_version" : "6.0.0-beta1"
# },
# "tagline" : "You Know, for Search"
# }
If you don't attach a header nginx
will return a 401 Unauthorized
error
$ curl -s http://localhost/es/
# {
# "message": "no Authorization header found"
# }
The server will also detect an expired or invalid token
$ curl -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...IVZPNrID8PIhg1o-g' http://localhost/es/
# {
# "message": "jwt signature verification failed: 'exp' claim expired at Fri, 19 Mar 2021 19:58:08 GMT"
# }
This will not protect against making requests directly to http://localhost:9200
but any requests through nginx
will be protected