In this post we will review the steps to integrate NGINX with OKTA. Most of the work displayed here is based on the post Use nginx to Add Authentication to Any Application.
Side note: In case you want an alternative for OKTA, check my other post: Using NGINX as a Reverse Proxy for Google OAuth 2.0.
We have an NGINX that serves as a reverse proxy for a backend site. Now we want to add authentication of the users using OKTA. This would cause any new user arriving to NGINX to authenticate with OKTA before proceeding to the backend site. To implement this we use the vouch-proxy which is handling the OIDC authentication protocol.
The Requests Flow
The requests flow is listed below:
- The user accesses the site URL
- NGINX configured with auth request sends the request headers to the vouch proxy
- The vouch proxy does not find any access token in the headers, and returns HTTP status 401
- NGINX redirects the user to the vouch proxy using a login URL
- The vouch proxy redirects the user to the OKTA authentication service.
- The user logins to OKTA
- OKTA redirects the user back to the site URL, and adds an access token header
- NGINX configured with auth request sends the request headers to the vouch proxy
- The vouch proxy does finds the access token in the headers, and sends it to the OKTA authentication service, which returns the user details.
- The vouch proxy returns HTTP status 200
- The NGINX proxy the request to the backend site
Implementation on Kubernetes
To implement on kubernetes, we use the sample backend site: http://okta-headers.herokuapp.com
(again, borrowed from the post Use nginx to Add Authentication to Any Application)
We add two DNS entries
- app1.alontest.com
- login.alontest.com
As this example uses NodePort, both of these IPs point to one of the kubernetes nodes IP.
Note that both of these DNS entries must share a common domain prefix (alontest.com) to allow cookies sharing.
Create an OKTA Application
To create an OKTA application, register to OKTA development site.
Once you have completed the registration, you will get into the OKTA admin, for example, in my case:
The OKTA base URL is shown in the OKTA dashboard:
Click on Applications, and then Add Application of type Web.
The application URLs should be updated with our vouch proxy URL.
In addition, the client ID and the client secret should be copied from here to the vouch proxy configuration (see in the next sections).
The NGINX Entities
The NGINX is deployed in kubernetes, and includes a deployment, a service and a configMap.
The NGINX service:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: okta
type: NodePort
ports:
- port: 80
targetPort: 8080
name: tcp-api
protocol: TCP
nodePort: 30000
The NGINX deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: okta
template:
metadata:
labels:
app: okta
spec:
terminationGracePeriodSeconds: 1
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
env:
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: nginx-config
configMap:
name: nginx-config
And the NGINX configMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
nginx.conf: |-
user nginx;
worker_processes 10;
error_log /dev/stdout warn;
pid /var/run/nginx.pid;
events {
worker_connections 10;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
resolver 8.8.8.8;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log off;
sendfile on;
port_in_redirect off;
proxy_max_temp_file_size 0;
server {
listen 8080;
server_name localhost;
# Any request to this server will first be sent to this URL
auth_request /vouch-validate;
location = /vouch-validate {
# This address is where Vouch will be listening on
proxy_pass http://vouch-service:80/validate;
proxy_pass_request_body off; # no need to send the POST body
proxy_set_header Content-Length "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# these return values are passed to the @error401 call
auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
}
error_page 401 = @error401;
# If the user is not logged in, redirect them to Vouch's login URL
location @error401 {
return 302 http://login.alontest.com:30001/login?url=http://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
}
location / {
proxy_pass http://okta-headers.herokuapp.com;
}
}
}
Some notes for the NGINX configuration:
- Our backend site is http://okta-headers.herokuapp.com
- We use auth_request to validate any request with the vouch proxy
- The location /vouch-validate is the configuration of how to access the vouch proxy
- In case of HTTP status 401, we redirect the user (using HTTP status 302) to the vouch-proxy. Notice that we send a "url" parameter in the query string to point back to where should the user be redirected once the authentication is complete.
The Vouch Proxy Entities
The vouch proxy is deployed in kubernetes, and includes a deployment, a service and a configMap.
The vouch proxy service:
apiVersion: v1
kind: Service
metadata:
name: vouch-service
spec:
selector:
app: vouch
type: NodePort
ports:
- port: 80
targetPort: 9090
name: tcp-api
protocol: TCP
nodePort: 30001
The vouch proxy deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vouch-deployment
spec:
replicas: 1
selector:
matchLabels:
app: vouch
template:
metadata:
labels:
app: vouch
spec:
terminationGracePeriodSeconds: 1
containers:
- name: vouch
image: voucher/vouch-proxy
imagePullPolicy: IfNotPresent
env:
volumeMounts:
- name: vouch-config
mountPath: /config
volumes:
- name: vouch-config
configMap:
name: vouch-config
And the vouch proxy configMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: vouch-config
data:
config.yml: |-
vouch:
logLevel: debug
testing: false
listen: 0.0.0.0 # VOUCH_LISTEN
port: 9090 # VOUCH_PORT
allowAllUsers: true
jwt:
maxAge: 240
compress: true
cookie:
name: VouchCookie
domain: alontest.com
secure: false
maxAge: 14400
sameSite: lax
session:
name: VouchSession
headers:
jwt: X-Vouch-Token # VOUCH_HEADERS_JWT
querystring: access_token # VOUCH_HEADERS_QUERYSTRING
redirect: X-Vouch-Requested-URI # VOUCH_HEADERS_REDIRECT
claims:
- groups
- given_name
test_url: http://yourdomain.com
post_logout_redirect_uris:
- http://myapp.yourdomain.com/login
- https://oauth2.googleapis.com/revoke
- https://myorg.okta.com/oauth2/123serverid/v1/logout?post_logout_redirect_uri=http://myapp.yourdomain.com/login
oauth:
provider: oidc
client_id: 0oauxxx18zEdyIR4hxxx
client_secret: aqvxxxTHi4uzqyFCVvGWTIXFiTFxxx27IloTN0_H
auth_url: https://dev-137493.okta.com/oauth2/default/v1/authorize
token_url: https://dev-137493.okta.com/oauth2/default/v1/token
user_info_url: https://dev-137493.okta.com/oauth2/default/v1/userinfo
end_session_endpoint: https://dev-137493.okta.com/oauth2/default/v1/logout
scopes:
- openid
- profile
callback_url: http://login.alontest.com:30001/auth
Some notes for the vouch proxy configuration:
- Use allowUsers:true, means that once a user is authenticated in OKTA, vouch proxy would permit its access, without additional limitations
- The cookie.domain specifies the common DNS prefix for the vouch proxy DNS and the NGINX DNS
- The cookie.secure: false enables us to use HTTP connections (and not HTTPS)
- The oauth.client_id and oauth.client_secret should be copied from the general section of the application that is created in OKTA admin site
- The oauth URLs parameters use the OKTA site URL (notice not to use the admin site by mistake, like I did at the beginning...)
- The callback URL is used to access back to the vouch proxy after the OKTA authentication is done
Final Note
In this post we have reviewed an NGINX and OKTA integration.
This is a simple example, but for production, you would probably change few of the configuration, like using a registering real DNS entries, using SSL for the NGINX.
auth_request_set $email $upstream_http_x_auth_request_email;
ReplyDeleteset_encode_base64 $encoded_string $email;
proxy_set_header Authorization "Basic $encoded_string";
$email in nginx log is getting correct value, but for encoded_string $email is coming as empty.
This part of the wonders of NGINX.
DeleteThe encoding is taking place before the auth request is sent.
If you want encoded email, you should try to return it encoded directly from the auth server.
Encode works on nginx if I do set_encode_base64 $encoded_string "test@example.com" this works, but the $email is not getting value in my example.
ReplyDeleteIn your example I saw $auth_resp_jwt used as "return 302 http://login.alontest.com:30001/login?url=http://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;"
$auth_resp_jwt is the variable set from auth_request_set.
--
I am not sure how to get encoded email from Oauth proxy.
to get the encoded email, you will need to return it from the auth request.
DeleteIn our case, the vouch server is the auth request service, so you need it to return the encoded header. You can fork the vouch server from https://github.com/vouch/vouch-proxy, and add the encoded header.
Hi Alon and Thanks for the post!
ReplyDeleteCorrect me if I'm wrong, but, after authentication against Okta, when the user will make another request to the backend site, NGINX will still send the request to Vouch in order to make sure this request is authenticated, right?
NGINX is not handling any aspect of the authentication, it will just "consult" with some authentication end-point for every request and the authentication end-point will decide whether the request is already authenticated or not by checking headers/cookies/parameters?
Thanks!
That's correct, NGINX will address the vouch for every request.
Delete