Skip to content

Nginx module for openid-connect relying party, SSO and JWT authentication

License

Notifications You must be signed in to change notification settings

tarachandverma/nginx-openidc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ecae8ca · Jan 7, 2022

History

45 Commits
May 20, 2018
Sep 8, 2018
May 20, 2018
Sep 8, 2018
Dec 28, 2017
Sep 8, 2018
Feb 20, 2016
Jan 7, 2022
Sep 8, 2018
Sep 8, 2018
Jul 14, 2017

Repository files navigation

nginx-openidc

Nginx module for openid connect relying party and JWT authentication

**nginx-openidc is Nginx module allows openid-connect(JWT) validation and access control based on standard claim as headers.

This document details the technical architecture and reasoning behind the nginx-openidc system.

The request flow - Relying Party ( RP )

  1. The user makes a request for a protected resource on RP ngx-oidc-demo.com for resource http://ngx-oidc-demo.com/protected.
  2. In "Access" phase of nginx, nginx-openidc performs a check RP session exists containing the logged-in userinfo.
  3. nginx-openidc decrypts cookie, verifies payload.
  4. On success, The nginx-openidc sets request headers X-OIDC-* i.e. X-OIDC-SUBJECT, X-OIDC-ISSUER and many more depending on scopes requested from JWT claim.
  5. In post "Access" phase, nginx-openidc oidc-config.xml rules are executed. This is most important phase where you can define unlimited authorization rules based on X-OIDC-* header
  6. Upon successfully running rules in post authorization phase, nginx forwards them to the backend service application.
  7. The service application can use the X-OIDC-* headers as-is.

The request flow - API Gateway

  1. ClientApp gets a JWT from authorized OP or generates a JWT using "client-credentials" flow
  2. The clientApp makes a request for a protected API on ngx-oidc-demo.com for resource with access_token as "Authorization: Bearer <id_token>" header. Examples http://ngx-oidc-demo.com/api/user.email Authorization: Bearer <id_token received from step(1)>
  3. In "Access" phase of nginx, nginx-openidc performs JWT validation.
  4. On success, The nginx-openidc sets request headers X-OIDC-* i.e. X-OIDC-SUBJECT, X-OIDC-ISSUER and many more depending on scopes requested from JWT claim.
  5. In post "Access" phase, nginx-openidc oidc-config.xml rules are executed.
  6. Upon successfully running rules in post authorization phase, nginx forwards them to the backend service application.
  7. The service application can use the X-OIDC-* headers as-is.

Here are the some of the features supported.

Features

  • Supports all OAuth2.0/OpenID-Connect flows
  • Supports HS256 and RS256
  • Supports rotated RS256 public key.
  • Implements Access phase which validates id_token(JWT) and passes claims as custom headers
  • Implements Post Authorization phase custom response based on custom headers.
  • Allows multiple relying party based on callback url
  • Allows multiple OpenID-Connect Provider
  • Supports "nonce" generation and validation
  • Supports relying party session
  • Removes custom headers from incoming request to ensure these headers are not spoofed.
  • Unlimited capabilities from Authorization to rewrites
  • Capabilites to add/remove/update request/response headers
  • Capability to generate custom error response
  • Support fo auto refresh entire oidc-config.xml and retrieve RS256 publicKeys from JWKS url periodically with no server restart

Supported platforms

  • All the flavors of *nix platforms, freebsd.

Installation

git clone https://github.com/tarachandverma/nginx-openidc.git
cd nginx-openidc
wget 'http://nginx.org/download/nginx-1.14.0.tar.gz'
tar -xzvf nginx-1.14.0.tar.gz
cd nginx-1.14.0/

 # Here we assume you would install you nginx under /opt/nginx/.
 ./configure --add-module=../src --with-http_ssl_module
 make
 make install

Test ( using docker )

#build docker image

  • docker build -t nginx-oidc .

#run docker image

  • docker run -p 80:80 -p 443:443 -i -t nginx-oidc

#add /etc/hosts entry

  • NEW-DOCKER-IP ngx-oidc-demo.com

#access docker container via protected path

Example :

X-OIDC-VALIDATE-STATUS = 	success
X-OIDC-ISSUER = 	https://accounts.google.com
X-OIDC-SUBJECT = 	113146716035256978692
X-OIDC-AUDIENCE = 	282412598309-545pvmsh9r23f4k1o7267744s59sod6v.apps.googleusercontent.com
X-OIDC-NONCE = 	a44df6ae-27f2-4c92-85e1-a22eb6381f53
X-OIDC-EMAIL = 	xxxx@gmail.com

X-REQUEST-METHOD = 	GET
X-REQUEST-SCHEME = 	http
X-RP-SESSION = 	7a6db077-ea64-47b4-ae77-2f438f4803ba

Note: headers available on successful JWT validation ( all header is prefixed with HTTP_ when it reaches to backend app )

Main Configuration

OPENIDC_HomeDir                        /usr/local/nginx/conf;
OPENIDC_LogFile                        oidc-refresh.log;
OPENIDC_SharedMemory  file=/config.shm size=61000;
OPENIDC_RemotePath uri=https://raw.githubusercontent.com/tarachandverma/nginx-openidc/master/example-conf/;
OPENIDC_PassPhrase                     secret123;
OPENIDC_HeaderPrefix                   X-OIDC-;
OPENIDC_RefreshWaitSeconds             20;
OPENIDC_ConfigFile                     oidc-config.xml;

  • OPENIDC_HomeDir Home directory to hold openid configuration and logs files.

Specify description string.

  • OPENIDC_LogFile

Log file is generated upon startup and refresh and records information whats loaded in shared memory.

  • OPENIDC_SharedMemory

Specifies shared memory file name and size

  • OPENIDC_RemotePath

Optional: Specifies remote directory to download oidc-config.xml during startup.

  • OPENIDC_PassPhrase

Specifies passPhrase to encrypt/decrypt relying party session

  • OPENIDC_HeaderPrefix

Specifies custom headers prefix for the claims, default:X-OIDC-

  • OPENIDC_RefreshWaitSeconds (mendatory if OP signing keys are rotating )

This will refresh entire oidc-config.xml remote repository defined by OPENIDC_RemotePath along with OP publicKeys defined by in oidc-config.xml default:no refresh

  • OPENIDC_ConfigFile

Specify relying party configuration and custom post Authorization response and rules

How to enable ngx openid-connect : nginx.conf


http {

	...
	OPENIDC_HomeDir                        /usr/local/nginx/conf;
	OPENIDC_LogFile                        oidc-refresh.log;
	OPENIDC_SharedMemory  file=/config.shm size=61000;
	OPENIDC_RemotePath uri=https://raw.githubusercontent.com/tarachandverma/nginx-openidc/master/example-conf/;
	OPENIDC_PassPhrase                     secret123;
	OPENIDC_HeaderPrefix                   X-REMOTE-;
	#OPENIDC_RefreshWaitSeconds				20;
	OPENIDC_ConfigFile                     oidc-config.xml;
	
	server {
			...
			# authorization code flow - exchanging authorization code to id_token(JWT)
		    location /internal/oauth2/token {
		    	internal;
		        proxy_pass https://www.googleapis.com/oauth2/v4/token;
		    }
	}
...
}


openid-connect configuration : oidc-conf.xml

<?xml version="1.0"?>
<oidcConfig>
   <oidcProviders>
	<!-- OpenID-Connect Provider metadata url -->	 
	 <oidcProvider id="$$unique identifier$$" issuer="$$OP issuer$$">
	 	<metadataUrl>$$OP metadata url$$</metadataUrl>
		<!--you can set individua params as well if metadata is not available -->
	 	<authorizationEndpoint>$$OP authorization_end_point$$</authorizationEndpoint>
	 	<tokenEndpoint>$$OP token_end_point same as defined in nginx.conf's proxy_pass for token endpoint$$</tokenEndpoint>
	 	<jwksUri>$$OP json web keys end poing$$</jwksUri><!-- json web keys url exposed by OP, useful if keys are rotated by OP -->
	 	<!-- json web keys in JSON format, useful if keys are not rotated by OP -->
	 	<jwksJson><![CDATA[{
 "keys": [
  $$key1 json$$,
  $$key2 json$$,
  $$key3 json$$,
  ...
  $$keyn json$$
 ]
}]]></jwksJson>
	<!-- example -->	 	
	 	<!--jwksJson><![CDATA[{
 "keys": [
  {
   "kty": "RSA",
   "alg": "RS256",
   "use": "sig",
   "kid": "2758a0d689ad62a453a671429a41d192d6bb7291",
   "n": "wjiigb39qOCj_WwYKqspYpk7SbuirXARcOo8bLzI95V3OIisz-40eOyo_SaeNeDb4KOcWZ9Qr0YmRnw0G8JhfVB973KwTURsUHQw3Q1fZSBw5T0vCMS5UHbMLxjHptCZ6kVRbBnRVuCMvY3ws0LCK9h9e4l7rCfXMQF3IlBUCisrGpYiVTO1-Mj3J6h_TJ7QDRQnqkX5jzcv-8XCRg_BefA40sC2fXjpZezdf8i27d3tuPRsjD46wFydDZk_KFBqSr8RE2EzJ6yaSNWnKByJejA6tNLK72UIrt0GE83TS1d369kGN2hk57gonlNMo-v5GTFweFNnQGU1xOng30bdvQ",
   "e": "AQAB"
  },
  {
   "kty": "RSA",
   "alg": "RS256",
   "use": "sig",
   "kid": "8a60d44a4ece38c0c9d16be136b2a1427b1d6ee0",
   "n": "l9Cn5qmr7VFKIed9RQIZPdenYp-V1BDM1wMg7Rpkl8mNQ7qsnJWuK8JKQoIljbwr9L1kKikKOR37z1prV0ZTQQao73YsB34OLbi25g0Xcr32j5giQDMxmFxmrYN1LG-T4qEOI4hE52Bafr5jBuZwL56HeuEU5G-QunGrNieGcfdVFXVW8-f9UBkJmoF_Dw_H38wNKif79tGKD2XXdG-VjT-Pg2nbpzQ6fIXunzaaW9aUyoNZWpQQ9_QBqgEApHAP5qWGQsUQLwzkiUr1Du51ERLC7nm3B-pLFqIlRmqiWVtUAdF0wkKaCQXyvFS2KabG9D4aX0T_MKESsV7T6wwvsQ",
   "e": "AQAB"
  },
  {
   "kty": "RSA",
   "alg": "RS256",
   "use": "sig",
   "kid": "eb29843dd7334cf989e1db6a2b0c6e07a10a9cd3",
   "n": "jTBFxlzE8GwPWvSRsiUIiZGp8QxhZNv9QMhoEIHaoyaTlHBGS3cmsqhEroFjW5dwiVee_WJFBd7IcoaBZgVmHgLVMCr7dDokcO2LWIgbPikS1gfCuD2twZyoGdVWRpLy1KGSFlINcn1bbvPpod6Bt3sEEIv-rSSR5sgRFJbPpKwIZDrZnZMRPVM6a29WJ129uo04hIpig3p7ULpFTJrAkkQAtfEtP_uFJXCF8DrG1HoM6xlVOR2ksqjkpQYAa7p5yPZGEn_oZDxeVSzjuKG5KRt7-UL7hSg3q4jdEeX60j6DAZEcxX8apIpmYG2gmACBWKWnguHRZXdqfofyBcKizQ",
   "e": "AQAB"
  },
  {
   "kty": "RSA",
   "alg": "RS256",
   "use": "sig",
   "kid": "0ec3739dba77e22d632f3484b687391a8956f96b",
   "n": "jA4LmJfj1zuYFYCEWJD62Di-LbuamWbTqdhSaNWCRovztCIY0UlbnoGluNpzePOHJSuocOgucQNybwpdCVJ9S0rCPccytDw1hAxcFqmHOeqGIdfnpCojF0m9pggleHgWI9biwWt1jbkAqNx7VK1gNUkhWoVp_iQGfE37DnWptdDMmh9wVMc2SY8wqxtkcZebYpRNE52t1HSnXJ6z_HtMYvoRpi35Ltv6tuB16vFgb70k3AiGfrK8llpo8VZBHax5MCAZM612zA24G3tYDPSPGksrnoUFL5Iwyv-Mf9y4y6kFlV5hIpC27VPJxXzc9jD1kMBmQxIArHpAVsea_C8eQQ",
   "e": "AQAB"
  }
 ]
}]]></jwksJson-->	 	
	 </oidcProvider>
   </oidcProviders>

	<!-- relying parties configuration, you can configure multiple relying parties, default must be configured and usually same as first one in case there is only one -->
	 <relyingParties>
	 	<relyingParty id="$$unique identifier1$$" clientID="$$YOUR CLIENT ID for app1$$" clientSecret="$$YOUR CLIENT SECRET for app1$$" validateNonce="true/false depending on if you want to validate JWT nonce">
	 		<description>nginx oidc demo</description>
	 		<redirectUri>$$CALLBACK URL WHERE ID_TOKEN OR CODE WILL BE RECIEVED$$</redirectUri>
	 		<issuer>$$OIDC ISSUER FROM ABOVE LIST TO SELECT APPROPRIATE TOKEN EXCHANGE END-POINT FOR ID_TOKEN$$</issuer>	 		
	 	</relyingParty>
	 	<relyingParty id="$$unique identifier2$$" clientID="$$YOUR CLIENT ID for app2 $$" clientSecret="$$YOUR CLIENT SECRET for app2$$" validateNonce="true/false depending on if you want to validate JWT nonce">
	 		<description>nginx oidc demo</description>
	 		<redirectUri>$$CALLBACK URL WHERE ID_TOKEN OR CODE WILL BE RECIEVED$$</redirectUri>
	 		<issuer>$$OIDC ISSUER FROM ABOVE LIST TO SELECT APPROPRIATE TOKEN EXCHANGE END-POINT FOR ID_TOKEN$$</issuer>	 		
	 	</relyingParty>
	 	...
	 	<relyingParty id="$$unique identifierN$$" clientID="$$YOUR CLIENT ID for appn $$" clientSecret="$$YOUR CLIENT SECRET for appn$$" validateNonce="true/false depending on if you want to validate JWT nonce">
	 		<description>nginx oidc demo</description>
	 		<redirectUri>$$CALLBACK URL WHERE ID_TOKEN OR CODE WILL BE RECIEVED$$</redirectUri>
	 		<issuer>$$OIDC ISSUER FROM ABOVE LIST TO SELECT APPROPRIATE TOKEN EXCHANGE END-POINT FOR ID_TOKEN$$</issuer>	 		
	 	</relyingParty>	 		 	
	 </relyingParties>
	 <!-- end of relying parties configuration -->
	 
 	 <!-- specify collection of actions inside the pageActions -->
     <oidcActions>	<!-- start of page-actions -->
 
		<!-- nginx authz handlers -->
	    <action id="oidc_version"><handler>oidc_version</handler></action>
	    <action id="oidc_config_core_status"><handler>oidc_config_core_status</handler></action>
	    <action id="oidc_rewrite_pageactions"><handler>oidc_rewrite_pageactions</handler></action>
	    <action id="oidc_rewrite_actionmappings"><handler>oidc_rewrite_actionmappings</handler></action>
	    <action id="oidc_rewrite_match"><handler>oidc_rewrite_match</handler></action>
	    <action id="oidc_headers"><handler>oidc_headers</handler></action>	    
	    <action id="oidc_index"><handler>oidc_index</handler></action>

        <action id="$$unique-action-name1$$" debug=$$true/false$$ type="$$login|callback|action$$"><!-- debug echo hostname of front-end box and/or echoes backend proxied box if its proxy request, default action type is authorize -->
    	    <description>describe what action does in few words</description> <!-- string -->
            <isForward>true/false</isForward> <!-- boolean set it to true if its internal redirect, false for 302 redirect, default value true -->
            <isPermanent>true/false</isPermanent> <!-- boolean, set it to true if permanent redirect 301 -->
            <base64UrlEncodeState>true/false</base64UrlEncodeState> <!-- boolean, set it to true if you want to base64encode current url instead of urlEncode passed in state -->            
            <regex>$$regex-to-generate-tokens-to-build-below-url$$</regex> <!-- string -->
            <advancedTemplate>true/false</advancedTemplate> <!-- string, set to true of below url is dynamically generated using cookie, request headers -->
            <uri>$$target-path1</uri> <!-- string, specify relative url if internal redirect or full path if external redirect 302 -->
    		<requestHeaders> <!-- array of header -->
        		<header name="$$header-name$$" do="add|set|append|merge|unset" matchList="$$match-list-name$$">$$header-value$$</header>
    		</requestHeaders>
    		<responseheaders> <!-- array of header -->
        		<header name="$$header-name$$" do="add|set|append|merge|unset" matchList="$$match-list-name$$">$$header-value$$</header>
    		</responseheaders>
			<oidcProvider>$$unique identifier of oidcProvider as defined in oidcProviders$$</oidcProvider>
			<relyingParty>$$unique identifier of RelyingParry as defined in relyingParties$$</relyingParty>                                 
        </action>                                                     
    </oidcActions><!-- end of page-actions -->

    <!-- matchLists are collection of individual matchList -->
    <!-- matchList is collection of individual matches evaluates as match1 OR match2 OR match3 ...  -->
    <!-- match is collection of individual conditions evaluates as host AND ip AND event AND header1 AND heder2 AND env1 ... -->
    <matchLists>  <!-- start of match-lists -->
       <!-- matchList returns true upon first match ie.evaluates condition as match1 OR match2 OR ... -->
       <matchList name="$$matchlist-name1$$">
       	    <!-- match return true if all elements matches ie.evaluates condition as host AND ip AND header1 AND header2 ... -->
       	    <!-- Any unspecified tag in match is considered matched ie if unspecified host matches all host  -->
            <match host=”$$host-name-regex$$”>
                <host>$$host-name-regex$$</host> <!-- string -->
            	<ip isregex="true/false" negate="true/false">$$client-ip-address-regex-or-string$$</ip> <!-- string -->
            	<path negate="true/false">$$path-regex-or-string$$</path> <!-- string -->
            	<event start="$$start-time$$" end="$$end-time$$" /> <!-- date time string ddd mmm MM HH:MM:SS YYYY format -->
                <header name="$$header-name1$$" isregex="$$true/false$$" negate=$$true/false$$ delimAnd="$$delimitor$$" >$$header-value1$$</header> 
                <header name="$$header-name2$$" isregex="$$true/false$$" negate=$$true/false$$ delimAnd="$$delimitor$$" >$$header-value2$$</header>
                .	.	. 
            </match>
            <match> <!-- match2 -->
            </match>
            <match> <!-- match3 -->
            </match>
        </matchList>
       <matchList name="$$matchlist-name1$$">
            <match host=”$$host-name$$”> 
                <header name="$$header-name1$$">$$header-value1$$</header> 
                <header name="$$header-name2$$">$$header-value2$$</header>
                .	.	. 
            </match> 
        </matchList>
        .	.	.
    </matchLists>
	
    <!-- locations are collection of individual mappings -->
    <!-- mappings is associated to source path and list of actions to choose  -->
    <locations> <!-- array -->
		<!-- nginx authz handlers to verify oidc information, mainly for debug purpose -->
	    <location path="^/oidc/version"><oidcAction>oidc_version</oidcAction></location>
	    <location path="^/oidc/config-status"><oidcAction>oidc_config_core_status</oidcAction></location>
	    <location path="^/oidc/rewrite-pageactions"><oidcAction>oidc_rewrite_pageactions</oidcAction></location>
	    <location path="^/oidc/rewrite-actionmappings"><oidcAction>oidc_rewrite_actionmappings</oidcAction></location>
	    <location path="^/oidc/rewrite-match"><oidcAction>oidc_rewrite_match</oidcAction></location>
	    <location path="^/oidc/headers"><oidcAction>oidc_headers</oidcAction></location>	    
	    <location path="^/oidc"><oidcAction>oidc_index</oidcAction></location>
	    <!-- end of nginx authz-->
        
        <location path="$$source-path-regex$$" matchLists="$$comma separated list of matchList$$" ignoreCase="true/false">
                <oidcAction matchList=$$matchlist to select this action$$>one of the action in above actions-list-1</oidcAction>
                <oidcAction matchList=$$matchlist to select this action$$>one of the action in above actions-list-2</oidcAction>
                .	.	.
                <oidcAction>last action, its default action if above action don't match</oidcAction>
        </location>
		.	.	.                                                                 
    </locations> <!-- end of locations -->
</oidcConfig>

mapping source url

  • path specifies uri patterns match on source uri

  • oidcAction specifies action taken in authorization where it can use all the x-oidc-* headers avaiable in the request

  • matchLists specifies the condition

action target action on source url

  • description specifies what action is all about in couple of words

  • isForward specifies internal forwared/redirect if set to true

  • isLoginRedirect specifies initial authorize request to OP meaning automatically adds state=CURRENT_URI and nonce=CSRF_AND_ID_TOKEN_REPLAY protection in initial request

  • uri specifies the target url which can be generated from source url

  • advancedTemplate specifies advanced usage to generate target url using various kind of format. Target url : http://hostname:port/myurl/%{format}

Following format-tags are supported.

           'r', requestVariables, 
           's', serverVariables,
           'c', requestCookie, 
           'u', urlDecodeToken, 
           'U', urlEncodeToken, 
           'q', requestQuery

Development

Diagnostics

  • Log file path
  • Startup log - /oidc-refresh.log

Related open source (OpenID connect Provider)

https://github.com/tarachandverma/easy-oidc-provider

Authors

[Tara chand Verma]


About

Nginx module for openid-connect relying party, SSO and JWT authentication

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages