post-thumb

The OAuth proxification guide for frontend developers with Microsoft EntraID

Basically for most frontend developers, OAuth integration means adding a third party authentication library to your application. But for some cases, having an OAuth proxy is a good solution to integrate your applications with a third party identity provider.

This article demonstrates how to create an OAuth proxy from zero with Nginx and EntraID as an identity provider.

Before starting, some definitions

Microsoft EntraID (ex Azure AD) is a cloud-based identity and access management service that provides a single sign-on (SSO) experience across multiple applications.

Tenant is a logical container for a set of users, applications, and resources. In our case an instance of Microsoft EntraID is a tenant.

Single sign-on (SSO) is a method of authenticating a user to a single application. The user is then automatically logged into the application without having to enter their credentials again. This is a common practice in web applications, where users are often required to log in to multiple applications to access their accounts.

Open authorization (OAuth) is an open standard for authorization that allows users to grant third-party applications access to their resources without sharing their credentials.

OpenID Connect is an open standard for authentication and authorization. It is an extension of OAuth 2.0 and provides a standardized way for applications to verify the identity of a user. OpenID Connect is a layer on top of OAuth 2.0 that provides a standardized way for applications to verify the identity of a user.

If you need more information about OAuth and OpenID workflows, please have a look to those didactic articles, OAuth from first principles and OpenID Connect and OAuth2 for the beginner

Reverse proxy is a surrogate server that appears to any client to be an ordinary web server, but in reality merely acts as an intermediary that forwards the client’s requests to one or more ordinary web servers. Reverse proxies can help increase scalability, performance, resilience, and security.

OAuth proxification is a technique that allows one or more web applications to be secured with a third party identity provider such as Azure EntraID without requiring to develop a security layer on top of the application. The session is managed by the OAuth proxy software, which is a lightweight proxy server that sits between the web application and the EntraID tenant.

This approach has several benefits:

  • With only one application created on Azure, you can have multiple secured applications behind the OAuth proxy to access resources
  • The session is maintained for all the web applications behind the OAuth proxy
  • No need to implement the OAuth flow for each web application through a third party library

How to setup an OAuth proxy ?

OAuth2-proxy is a lightweight proxy server that sits between the web application and the EntraID tenant, it’s a reverse proxy that provides authentication with Google, Azure, OpenID Connect and many more identity providers.

$ go install github.com/oauth2-proxy/oauth2-proxy/v7@latest
$ oauth2-proxy 

2025-01-10 12:00:00 INFO  [oauth2-proxy] - Starting oauth2-proxy version 7.2.0
2025-01-10 12:00:00 INFO  [oauth2-proxy] - Listening on :4180
2025-01-10 12:00:00 INFO  [oauth2-proxy] - Provider: Azure
2025-01-10 12:00:00 INFO  [oauth2-proxy] - Session cookie name: _oauth2_proxy

2025-01-10 12:01:00 INFO  [oauth2-proxy] - Request received: GET /oauth2/start
2025-01-10 12:01:00 DEBUG [oauth2-proxy] - Redirecting to authorization URL: https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?client_id=abc123&redirect_uri=https%3A%2F%2Fmyapp.com%2Foauth2%2Fcallback&response_type=code&scope=openid%20email%20profile&response_mode=query

The full documentation is available here

Architecture Overview

In our example we will use Azure EntraID as an identity provider.

overview.png

I wanted to create an end-to-end basic example to show how OAuth proxification works for simple web application with a front, a backend and a database. The only specific component is the OAuth proxy.

I decided to have 2 Nginx Frontends:

  • One to handle the requests coming from the DNS record that will also handle the oauth proxyfication.
  • And another to handle all the secured application endpoints.

It has the advantage of bringing together oauth secured and unsecured applications in the same architecture. Also it divides the authentication and authorization part of the frontend configuration.

1. Prepare the EntraID tenant and create an Application

If you are using an already existing OAuth tenant , retrieve your company tenant ID, client ID and client secret.

If you don’t have one, or you need a developer tenant, create a new one as follow:

  • You can have a free trial of Azure subscription here
  • Account creation allows you to generate mock user accounts for the tenant with admin and user mailboxes.
  • You can after register an application with its callback for the OAuth redirection here

At this stage, you should have the following informations at your disposal:

  • An admin email used to create the tenant.
  • A tenant ID
  • A client ID
  • A client secret

You can now define some important URLs for the OAuth proxy configuration available in the openid-configuration endpoint of your tenant by adapting the following URL:

You can check now that you have similar authentication URLs available in this openid-configuration as below:

DescriptionURL
Issuer URLhttps://login.microsoftonline.com/_your_tenant ID_/v2.0
Login URLhttps://login.microsoftonline.com/_your_tenant_ID_/oauth2/authorize
Redeem URLhttps://login.microsoftonline.com/_your_tenant_ID_/oauth2/token
Profile URLhttps://graph.microsoft.com/v1.0/me

💡 How to test/debug your tenant with Microsoft graphAPI ?

  1. On you browser devtools, you can view your bearer token from headers by adding the proper Nginx instruction add_header (cf. Nginx configurations below).

  2. Then, to decode the JWT token, you can use the Microsoft JWT decoder online

  3. Also, Graph explorer API (GUI) is a great tool to test your tenant. Notice that a beta version is available less reliable but can contain more data than the public version.

2. Create your OAuth proxy server

OAuth proxification overview

You can download and install the go binary from the official website . Of course you can create a dedicated linux service & user account to run the OAuth proxy. The default port is 4180.

To configure the identity provider on the proxy, you have basically 3 options:

  • Use the command line parameters
  • User environment variables $OAUTH2_PROXY_….
  • Use the configuration file /etc/oauth2_proxy.cfg

I choosed to use environment variables because the configuration file seems not to support all the features of the OAuth proxy.

🗒️ oauth-service-script.sh
# Azure EntraID
export OAUTH2_PROXY_PROVIDER=oidc # Azure is also supported for simpler configuration
export OAUTH2_PROXY_CLIENT_ID=<YOUR_TENANT_CLIENT_ID>
export OAUTH2_PROXY_CLIENT_SECRET=<YOUR_TENANT_SECRET>
export OAUTH2_PROXY_AZURE_TENANT=<YOUR_TENANT_ID>
export OAUTH2_PROXY_REDIRECT_URL=XXXXX
export OAUTH2_PROXY_OIDC_ISSUER_URL=https://sts.windows.net/<YOUR_TENANT_ID>/
export OAUTH2_PROXY_SCOPE=openid email profile # Scopes define the level of access you want to grant to the application it relies on EntraID application configuration
#export OAUTH2_PROXY_INSECURE_OIDC_SKIP_ISSUER_VERIFICATION=true
#export OAUTH2_PROXY_SKIP_CLAIMS_FROM_PROFILE_URL=true
#export OAUTH2_PROXY_USER_ID_CLAIM=name
#export OAUTH2_PROXY_OIDC_GROUPS_CLAIM=groups
#export OAUTH2_PROXY_OIDC_EMAIL_CLAIM=email
#export OAUTH2_PROXY_OIDC_AUDIENCE_CLAIM=name
#export OAUTH2_PROXY_PROFILE_URL=https://graph.microsoft.com/v2.0/me
#export OAUTH2_PROXY_LOGIN_URL=https://login.microsoftonline.com/<YOUR_TENANT_ID>/oauth2/v2.0/authorize
#export OAUTH2_PROXY_REDEEM_URL=https://login.microsoftonline.com/<YOUR_TENANT_ID>/v2.0/token


# HTTP / HTTPS
export OAUTH2_PROXY_HTTP_ADDRESS=http://0.0.0.0:4180

# We don't need HTTPS because we use https is managed upstream of the auth Nginx ( by a load balancer / a reverse proxy )
#export OAUTH2_PROXY_HTTPS_ADDRESS=https://0.0.0.0:443
#export OAUTH2_PROXY_TLS_CERT_FILE=/oauth2/cert.pem 
#export OAUTH2_PROXY_TLS_KEY_FILE=/oauth2/cert.key
#export OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY=true
#export OAUTH2_PROXY_FORCE_HTTPS=true


# HEADERS & COOKIES
export OAUTH2_PROXY_COOKIE_EXPIRE=24h
export OAUTH2_PROXY_COOKIE_SECURE=true  
export OAUTH2_PROXY_COOKIE_SECRET=XXXXXXXX
export OAUTH2_PROXY_EMAIL_DOMAINS=*
#export OAUTH2_PROXY_COOKIE_NAME=nexusAuth
#export OAUTH2_PROXY_COOKIE_SAMESITE=lax
#export OAUTH2_PROXY_COOKIE_CSRF_PER_REQUEST=true
#export OAUTH2_PROXY_COOKIE_CSRF_EXPIRE=5m
#export OAUTH2_PROXY_COOKIE_DOMAINS=.localhost
#export OAUTH2_PROXY_COOKIE_HTTPONLY=true

export OAUTH2_PROXY_REVERSE_PROXY=true
# If you have web sockets enabled in your application, you can enable the proxy to handle them as well
export OAUTH2_PROXY_PROXY_WEBSOCKETS=true
# if you want to pass the user's email address to the upstream application, you can enable this option
export OAUTH2_PROXY_SET_XAUTHREQUEST=true
#export OAUTH2_PROXY_PASS_USER_HEADERS=true
#export OAUTH2_PROXY_PREFER_EMAIL_TO_USER=true
#export OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS=true
#export OAUTH2_PROXY_PASS_ACCESS_TOKEN=true

# PASS AZURE AD BEARER TOKEN ( usefull to debug or to use the graph API on your applications)
# NB : YOU NGINX CONFIGURATION MUST BE SET TO PASS THE AUTHORIZATION HEADER (add_header_proxy and for debug add_header)
export OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=true
export OAUTH2_PROXY_SET_AUTHORIZATION_HEADER=true

export OAUTH2_PROXY_PASS_BASIC_AUTH=false
#export OAUTH2_PROXY_PASS_HOST_HEADER=true
#export OAUTH2_PROXY_SESSION_STORE_TYPE=cookie
#export OAUTH2_PROXY_WHITELIST_DOMAINS=.localhost

export OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
export OAUTH2_PROXY_SHOW_DEBUG_ON_ERROR=true
export OAUTH2_PROXY_AUTH_LOGGING=true
export OAUTH2_PROXY_REQUEST_LOGGING=true
export OAUTH2_PROXY_STANDARD_LOGGING=true   
#export OAUTH2_PROXY_APPROVAL_PROMPT=consent
#export OAUTH2_PROXY_UPSTREAMS=http://localhost:8081

<PATH_OF_YOUR_BINARY>/oauth2-proxy

3. Create a domain level Nginx front to handle the OAuth authentication

OAuth proxification overview

The callback URL is the URL where the OAuth proxy will redirect the user after the authentication process. It must be a valid endpoint configured on your Nginx server. The callback URL can be configured in the Azure EntraID application settings that you created earlier . Also you can find endpoints provided by the OAuth proxy here .

EndpointDescription
/oauth2/sign_inThe login page, which also doubles as a sign-out page (it clears cookies)
/oauth2/sign_outThis URL is used to clear the session cookie
/oauth2/callbackThe URL used at the end of the OAuth cycle. The oauth app will be configured with this as the callback URL.
/oauth2/userinfoThe URL is used to return user’s email from the session in JSON format.
/oauth2/authOnly returns a 202 Accepted response or a 401 Unauthorized response; for use with the Nginx auth_request directive

Here is the Nginx configuration to handle the secured application endpoints:

🗒️ nginx-oauth.conf
map $http_upgrade $connection_upgrade {
        default upgrade;
        ''	close;
    }

server {
# We don't need HTTPS because we use https is managed upstream of the auth Nginx ( by a load balancer / a reverse proxy )
#    listen 443 default ssl;
#    server_name localhost;
#    ssl_certificate /etc/nginx/cert.pem;
#    ssl_certificate_key /etc/nginx/cert.key;
     listen	  80 default_server;
     server_name localhost
     listen	  [::]:80 default_server;
     server_name  _;

     add_header Strict-Transport-Security max-age=2592000;

     # OAuth proxy cookie can require to increase the buffer size
     proxy_buffering on;
     proxy_buffer_size 8k;

  location /oauth2/ {
    proxy_pass       http://IP_OF_YOUR_OAUTH_PROXY:4180;
    proxy_set_header Host                    $host;
    proxy_set_header X-Real-IP               $remote_addr;
    proxy_set_header X-Auth-Request-Redirect $request_uri;
  }

  location /oauth2/auth {
    proxy_pass       http://IP_OF_YOUR_OAUTH_PROXY:4180;
    proxy_set_header Host             $host;
    proxy_set_header X-Real-IP        $remote_addr;
    proxy_set_header X-Forwarded-Uri  $request_uri;
    proxy_set_header Content-Length   "";
    proxy_pass_request_body           off;
  }

 location /securedapps {
    auth_request /oauth2/auth;
    error_page 401 = /oauth2/sign_in;

    # pass information via X-User and X-Email headers to backend,
    # requires running with --set-xauthrequest flag

    auth_request_set $email	 $upstream_http_x_auth_request_email;
    #add_header X-SSO-MAIL $email;
    proxy_set_header X-SSO-MAIL $email;
    # return 200 $email;

    #auth_request_set $user       $upstream_http_x_auth_request_user;
    #auth_request_set $user       $upstream_http_x_auth_request_preferred_username;
    #proxy_set_header X-User $user;

    auth_request_set $authorization_id  $upstream_http_authorization;
    # Passing the EntraID token clear on the client. FOR DEBUG ONLY
    # add_header X-SSO-AUTHORIZATION-ID $authorization_id;
    # proxy_set_header X-SSO-AUTHORIZATION-ID $authorization_id;

    # if you enabled --pass-access-token, this will pass the token to the backend
    #auth_request_set $token  $upstream_http_x_auth_request_access_token;
    #proxy_set_header X-ACCESS-TOKEN $token;

# This is how to secure your application endpoints, the oauth proxy will call the identity provider to authenticate the user and redirect to the second Nginx server
    proxy_pass http://IP_OF_THE_SECOND_NGINX_SERVER:PORT;

  }
}

4. Create your secured Enpoint defining OAuth secured application endpoints

OAuth proxification overview

Now it’s time to create your secured application endpoints. You can use the same Nginx server as the one used to handle the OAuth authentication. The configuration is basic.

🗒️ nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    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  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-available/*.conf;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /path/to/your/root/static/public_html;


       # reverse proxy to your secured application
        location /securedapp1 {
            proxy_pass http://IP_OF_YOUR_SECURED_APPLICATION1:8080;
        }

       # static content served
        location /securedapp2 {
            alias /path/to/your/secured/application2/public_html;

        }
    }
}

The proxy is now securing your apps and pass correctly the session cookie on headers. You can retrieve more user information (ex:the employee id) from the tenant on secured apps thanks to the MS Graph API with the token passed in at the header of each requests.

5. Containerize your architecture (If needed)

Of course all the previous steps are containerizable.

Here is an example of a docker-compose. You can also use a Kubernetes manifest to deploy the architecture.

🗒️ docker-compose.yml
services:
  ###########################
  #     Front  with Auth    #
  ###########################
  authfront:
    image: nginx
    depends_on:
        - oauth2
        - securedappsfront
    volumes:
      - ./nginx-oauth2.conf:/etc/nginx/conf.d/oauth2.conf
    ports:
      - "80:80"
      - "443:443"
   
  #########################
  #     AzureAD OAuth     #
  #########################
  oauth2:
    build: 
      context: .
      dockerfile: oauth2.Dockerfile
    environment:
      - OAUTH2_PROXY_PROVIDER=oidc
      - OAUTH2_PROXY_CLIENT_ID=<YOUR_CLIENT_ID>
      - OAUTH2_PROXY_CLIENT_SECRET=<YOUR_CLIENT_SECRET>
      - OAUTH2_PROXY_AZURE_TENANT=<YOUR_TENANT_ID>
      - OAUTH2_PROXY_OIDC_ISSUER_URL=https://login.microsoftonline.com/<YOUR_TENANT_ID>/v2.0
      - OAUTH2_PROXY_LOGIN_URL=https://login.microsoftonline.com/<YOUR_TENANT_ID>/oauth2/authorize"
      - OAUTH2_PROXY_REDEEM_URL=https://login.microsoftonline.com/<YOUR_TENANT_ID>/oauth2/token
      - OAUTH2_PROXY_REDIRECT_URL=https://<YOUR_DOMAIN>/oauth2/callback
      - OAUTH2_PROXY_PROFILE_URL=https://graph.microsoft.com/v1.0/me
      - OAUTH2_PROXY_SCOPE=openid email profile
      - OAUTH2_PROXY_HTTP_ADDRESS=http://0.0.0.0:4180
      - OAUTH2_PROXY_COOKIE_SECURE=true  
      - OAUTH2_PROXY_COOKIE_SECRET=<YOUR_COOKIE_SECRET>
      - OAUTH2_PROXY_PROXY_WEBSOCKETS=true
      - OAUTH2_PROXY_EMAIL_DOMAINS=*
      - OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
      - OAUTH2_PROXY_COOKIE_EXPIRE=24h
      - OAUTH2_PROXY_PASS_USER_HEADERS=true
      - OAUTH2_PROXY_PASS_BASIC_AUTH=true
      - OAUTH2_PROXY_REVERSE_PROXY=true
      - OAUTH2_PROXY_SET_XAUTHREQUEST=true
      - OAUTH2_PROXY_SHOW_DEBUG_ON_ERROR=true
      - OAUTH2_PROXY_AUTH_LOGGING=true
      - OAUTH2_PROXY_REQUEST_LOGGING=true
    #volumes:
    #  - ./oauth2-proxy.cfg:/etc/oauth2-proxy.cfg
    # ⚠️ Be carefull in production by using rootless configuration
    command: /bin/sh -c "/root/go/bin/oauth2-proxy" #--config=/etc/oauth2-proxy.cfg"
    depends_on:
      - securedfront

  ###########################
  #     securedappsfront    #
  ###########################

  securedfront:
      image: nginx 
      volumes:
        - ./nginx.conf:/etc/nginx/nginx.conf

      depends_on:
      - securedapp1
      - api

  ##################
  #      app 1     #
  ##################
  securedapp2:
    build: 
      context: .
      dockerfile: myimage.Dockerfile
    depends_on:
      - api

  ###############
  #     API     #
  ###############
  
  api:
    image: openjdk:11.0.16-jdk
    depends_on:
      - postgres 

  ###############
  #     BDD     #
  ###############

  postgres:
    image: docker.io/postgres:13
    environment:
      POSTGRES_USER: "${POSTGRES_USER}"
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_DB: "${POSTGRES_DB}"
      PGDATA: "${PGDATA}"
    restart: always
    volumes:
      - ./postgres:/data/postgres
🗒️ oauth2.Dockerfile
FROM redhat/ubi8

RUN dnf module -y install go-toolset && dnf -y clean all  && rm -rf /var/cache
RUN go install github.com/oauth2-proxy/oauth2-proxy/v7@latest

Conclusion

An OAuth proxy is a useful tool to integrate your application with a third party identity provider. It offers more flexibility than the traditional OAuth third party library integration (such as Svelte Auth, Vue Auth, etc.).

Finally, we just implemented the Backend For Frontend (BFF) architecture pattern. In this architecture, the BFF runs as a server-side component, but it is a component of the frontend application.

Traefik plugins

To go further, other options can be considered in a multi-services architecture. You can use the OAuth proxification plugin from an API gateway (like Kong) or from a more advanced reverse proxy (such as Caddy or Traefik). It permit to simplify the configuration and hide the complexity of the OAuth proxification but the tradeoff is that you depend on third party plugin of your API gateway/reverse proxy.