Introduction
This is the fourth post, in a series of posts about adding support for Authentication (AuthN) to HAPI FHIR with OAuth2 Proxy, Nginx and Keycloak:
- Add AuthN to HAPI FHIR with OAuth2 Proxy, Nginx and Keycloak - Part 1
- Add AuthN to HAPI FHIR with OAuth2 Proxy, Nginx and Keycloak - Part 2
- Add AuthN to HAPI FHIR with OAuth2 Proxy, Nginx and Keycloak - Part 3
- Add AuthN to HAPI FHIR with OAuth2 Proxy, Nginx and Keycloak - Part 4
OAuth2 Proxy
OAuth2 Proxy is an open source tool that can be used to interecept requests to your application and redirect them to an Authorisation server that supports OAuth 2.0 and OpenID Connect.
I followed the recommendations in the following guides:
- OAuth2 Proxy docs: TLS Configuration
- OAuth2 Proxy docs: Integration
TLS Configuration
All traffic is routed through Nginx and TLS is terminated at Nginx (the reverse proxy).
Integration
OAuth2 Proxy forwards subrequests to Keycloak in order to authenticate requests to HAPI FHIR. Take a look at the auth_request
directive section, in this post.
Config Options
OAuth2 Proxy is running behind Nginx (the reverse proxy):
oauth2-proxy:
container_name: oauth2-proxy
...
environment:
...
# Proxy options
OAUTH2_PROXY_EMAIL_DOMAINS: '*'
OAUTH2_PROXY_REDIRECT_URL: ${PROTOCOL}://${OAUTH2_PROXY_HOSTNAME}/oauth2/callback
OAUTH2_PROXY_REVERSE_PROXY: true
OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY: true
OAUTH2_PROXY_WHITELIST_DOMAINS: ${OAUTH2_PROXY_HOSTNAME}:${OAUTH2_PROXY_PORT}
...
We want to use the OpenID Connect (oidc) provider:
oauth2-proxy:
container_name: oauth2-proxy
...
environment:
...
# General Provider options
OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}
OAUTH2_PROXY_CODE_CHALLENGE_METHOD: S256
OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL: true
OAUTH2_PROXY_OIDC_ISSUER_URL: ${PROTOCOL}://${KEYCLOAK_HOSTNAME}:${KEYCLOAK_PORT}/realms/${KEYCLOAK_REALM}
OAUTH2_PROXY_PROVIDER: oidc
OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: OpenID Connect
OAUTH2_PROXY_SCOPE: ${SCOPE}
...
OAuth2 Proxy will check that the Issuer URL matches the Issuer URL (iss) returned by Keycloak.
You can preview tokens in the Keycloak Admin Console, for example:
The complete OAuth2 Proxy configuration:
oauth2-proxy:
container_name: oauth2-proxy
build:
context: ./services/oauth2-proxy
dockerfile: Dockerfile
restart: unless-stopped
command:
[
'--standard-logging=true',
'--auth-logging=true',
'--request-logging=true',
]
environment:
# General Provider options
OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}
OAUTH2_PROXY_CODE_CHALLENGE_METHOD: S256
OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL: true
OAUTH2_PROXY_OIDC_ISSUER_URL: ${PROTOCOL}://${KEYCLOAK_HOSTNAME}:${KEYCLOAK_PORT}/realms/${KEYCLOAK_REALM}
OAUTH2_PROXY_PROVIDER: oidc
OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: OpenID Connect
OAUTH2_PROXY_SCOPE: ${SCOPE}
# Cookie Options
OAUTH2_PROXY_COOKIE_CSRF_PER_REQUEST: true
OAUTH2_PROXY_COOKIE_EXPIRE: 30m
OAUTH2_PROXY_COOKIE_HTTPONLY: true
OAUTH2_PROXY_COOKIE_NAME: ${COOKIE_NAME}
OAUTH2_PROXY_COOKIE_REFRESH: 25m
OAUTH2_PROXY_COOKIE_SAMESITE: lax
OAUTH2_PROXY_COOKIE_SECRET: ${COOKIE_SECRET}
OAUTH2_PROXY_COOKIE_SECURE: true
# Header options
OAUTH2_PROXY_PASS_ACCESS_TOKEN: true
OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER: true
OAUTH2_PROXY_PROXY_HEADERS: xforwarded
OAUTH2_PROXY_SET_AUTHORIZATION_HEADER: true
OAUTH2_PROXY_SET_XAUTHREQUEST: true
# Logging options
OAUTH2_PROXY_ERRORS_TO_INFO_LOG: true
OAUTH2_PROXY_REQUEST_LOGGING: true
OAUTH2_PROXY_SILENCE_PING_LOGGING: true
# Page Template options
OAUTH2_PROXY_SHOW_DEBUG_ON_ERROR: true
# Proxy options
OAUTH2_PROXY_EMAIL_DOMAINS: '*'
OAUTH2_PROXY_REDIRECT_URL: ${PROTOCOL}://${OAUTH2_PROXY_HOSTNAME}/oauth2/callback
OAUTH2_PROXY_REVERSE_PROXY: true
# OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: true
OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY: true
OAUTH2_PROXY_WHITELIST_DOMAINS: ${OAUTH2_PROXY_HOSTNAME}:${OAUTH2_PROXY_PORT}
# Server options
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:${OAUTH2_PROXY_PORT}
# Session options
OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis
OAUTH2_PROXY_SESSION_STORE_TYPE: redis
# Upstreams configuration
OAUTH2_PROXY_SSL_UPSTREAM_INSECURE_SKIP_VERIFY: true
OAUTH2_PROXY_UPSTREAMS: http://hapi-fhir:8080/
env_file: ./.env
ports:
- 4180:4180
depends_on:
redis:
condition: service_healthy
keycloak.au.localhost:
condition: service_healthy
networks:
- hapi_fhir_network
See: docker-compose.yml
The .env
file:
PROTOCOL=https
# Postgres
POSTGRES_DB=hapi-fhir
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret
# Keycloak
KEYCLOAK_HOSTNAME=keycloak.au.localhost
KEYCLOAK_PORT=8443
KEYCLOAK_REALM=hapi-fhir-dev
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=secret
# OAuth Client
CLIENT_ID=oauth2-proxy
CLIENT_SECRET=aHkRec1BYkfaKgMg164JmvKu8u9iWNHM
SCOPE=openid
# OAuth2 Proxy
OAUTH2_PROXY_HOSTNAME=hapi-fhir.au.localhost
OAUTH2_PROXY_PORT=4180
COOKIE_NAME=SESSION
COOKIE_SECRET=uzVUu9BdSpOXqPeMaGoTYuTHazRXWoUCajyLUfWlnv8=
See: .env
Note: Docker will look for your .env
file in the same directory as your Docker Compose configuration file.
Source Code
References
System Hardening
- ASD: Implementing Certificates, TLS, HTTPS and Opportunistic TLS
- Cloudflare docs: Cipher suites recommendations
OAuth 2.0
- IETF: OAuth 2.0 for Browser-Based Applications
- Spring docs: Implementation Guidelines for Browser-Based Applications
- okta Developer blog: OAuth for Java Developers
- OAuth.com: OAuth 2.0 Playground
Keycloak
- Keycloak guides: Configuring Keycloak for production
- Keycloak guides: Configuring TLS
- Keycloak guides: Configuring trusted certificates
- Keycloak guides: Configuring the hostname
- Keycloak guides: Using a reverse proxy
- Keycloak guides: Running Keycloak in a container
Nginx
- Nginx docs: NGINX SSL Termination
- Nginx docs: Authentication Based on Subrequest Result
OAuth2 Proxy
- OAuth2 Proxy docs: Integration
- OAuth2 Proxy docs: TLS Configuration