IBM Verify

 View Only

Creating OIDC .well-known endpoints with LUA HTTP transformation rules in IBM Security Verify Access

By Lachlan James Gleeson posted Thu December 21, 2023 10:15 PM

  

IBM Security Verify Access has extensive support for many OIDC Authentication workflows. However the metadata documents created for API Clients do not conform to the OIDC Discovery specifications. This is a conscious decision, since Verify Access is capable of supporting multiple API Clients with different configurations.

This blog will demonstrate how LUA HTTP transformation rules can be configured to support the OIDC Discovery specification; and how administrators can extend these rules to return different openid-configuration documents based on the incoming request (eg: host name, headers)

LUA HTTP transformation rule:

The following code snippet is an example LUA HTTP transformation rule, which is capable of returning JSON responses for the /.well-known/openid-configuration and /.well-known/jwks.json endpoints:

--[[
        A simple transformation that returns metadata for the OIDC well known URL.

        Activated in Reverse Proxy config with:

        ================
        [http-transformations]
        wellknown = wellknown.lua

        [http-transformations:wellknown]
        request-match = request:GET /.well-known/*
        =============
--]]


function return_error()
        HTTPResponse.setStatusCode(404)
        HTTPResponse.setStatusMsg("NOT FOUND")
        HTTPResponse.setHeader("Content-Type", "text/plain")    
    HTTPResponse.setBody("404 Not Found!")
    Control.responseGenerated(true)
end

if HTTPRequest.getURL() ==  "/.well-known/openid-configuration" then
    host = HTTPRequest.getHeader("host")
        HTTPResponse.setStatusCode(200)
        HTTPResponse.setStatusMsg("OK")
        HTTPResponse.setHeader("Content-Type", "application/json")  
    HTTPResponse.setBody([[{
  "token_endpoint": "https://]] .. host .. [[/mga/sps/oauth/oauth20/token",
  "token_endpoint_auth_methods_supported": [
    "private_key_jwt",
    "client_secret_post",
    "client_secret_basic",
    "tls_client_auth",
    "self_signed_tls_client_auth"
  ],
  "jwks_uri": "https://]] .. host .. [[/.well-known/jwks.json",
  "response_modes_supported": [
    "fragment",
    "form_post"
  ],
  "subject_types_supported": [
    "pairwise"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "response_types_supported": [
    "code",
    "id_token",OAuth and OpenID Connect Provider Configuration
    "code id_token",
    "id_token token"
  ],
  "scopes_supported": [
    "openid",
    "profile",
    "email"
  ],
  "issuer": "https://]] .. host .. [[",
  "request_uri_parameter_supported": false,
  "userinfo_endpoint": "https://]] .. host .. [[/mga/sps/oauth/oauth20/userinfo",
  "authorization_endpoint": "https://]] .. host .. [[/mga/sps/oauth/oauth20/authorize",
  "device_authorization_endpoint": "https://]] .. host .. [[/mga/sps/oauth/oauth20/device_authorize",
  "http_logout_supported": true,
  "frontchannel_logout_supported": true,
  "end_session_endpoint": "https://]] .. host .. [[/mga/sps/oauth/oauth20/logout",
  "claims_supported": [
    "sub",
    "iss",
    "aud",
    "exp",
    "iat",
    "acr",
    "cnf",
    "nonce",
    "preferred_username",
    "name",
    "email"
  ]
}]])
    Control.responseGenerated(true)
elseif HTTPRequest.getURL() ==  "/.well-known/jwks.json" then
    host = HTTPRequest.getHeader("host")
    if host == "www.myidp.ibm.com" then
        -- Replace APIProtectionDefinition with your OIDC Definition name.
        -- The /mga prefix may differ in your environment, based on the junction created when the 
        --          "OAuth and OpenID Connect Provider Configuration" wizard is run.
        HTTPRequest.setURL("/mga/sps/oauth/oauth20/jwks/APIProtectionDefinition") 
    else
        -- Use another OIDC Definition or
        return_error()
    end
else
    return_error()
end

This code demonstrates two ways that an administrator can return a metadata document: by either statically defining the JSON in the transformation rule; or by redirecting the request to the appropriate OIDC API protection definition.

Static JSON document:

The openid-configuration example demonstrates how the host name from the request can be extracted and returned in all of the endpoints defined in the JSON document. This may be useful if a single Verify Access appliance is configured for several domain names or IP addresses. However it can cause the transformation rule to become quite large, particularly if several different OpenID configurations are supported. This approach does give administrators the ability to support multiple host names or IP addresses for a single API protection definition.

Redirect to API protection definition metadata:

The jwks.json example demonstrates how a request can be rewritten to return a document hosted by the Verify Access runtime server. This may be useful if Verify Access hosts several API protection definitions for different domain names or with significantly different capabilities. Note that the transformation rule first extracts the host name from the request, then rewrites the request to correlate a host name with a specific API protection definition. This could be modified to use other attributes from the request, such as a specific header name/value.

WebSEAL Reverse Proxy configuration:

Enabling this HTTP transformation rule requires an administrator to modify some of the stanza in the WebSEAL configuration file for your reverse proxy instance:

[http-transformations]
wellknown = wellknown.lua

[http-transformations:wellknown]
request-match = request:GET /.well-known/*

The http-transformations stanza will already exist in the configuration file, you simple need to add the transformation rule with a meaningful name. Next you need to create a new stanza for the name you just created, along with the request matching configuration for the rule. The provided example will simply run our LUA rule for any request which start with the /.well-known/ prefix.

Testing it out:

If you have configured your rule correctly, you should now be able to make a request to the /.well-known/openid-configuration endpoint of your reverse proxy and get a JSON response similar to the following:

> curl -kv -X GET  https://192.168.130.11:30443/.well-knwon/openid-configuration

{"issuer":"https://192.168.130.11:30443",
"authorization_endpoint":"https://192.168.130.11:30443/mga/sps/oauth/oauth20/authorize",
"token_endpoint":"https://192.168.130.11:30443/mga/sps/oauth/oauth20/token",
"userinfo_endpoint":"https://192.168.130.11:30443/mga/sps/oauth/oauth20/userinfo",
"jwks_uri":"https://192.168.130.11:30443/mga/sps/oauth/oauth20/jwks/ILMTIntegration",
"response_types_supported":["code","none"],
"response_modes_supported":["fragment","form_post"],
"subject_types_supported":["public"],
"token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_post","client_secret_basic","tls_client_auth","self_signed_tls_client_auth"],
"grant_types_supported":["client_credentials","authorization_code","refresh_token"],
"id_token_signing_alg_values_supported":["RS256"],
"id_token_encryption_alg_values_supported":[],
"id_token_encryption_enc_values_supported":[],
"poc":"https://192.168.130.11:30443/mga/",
"name":"ILMTIntegration",
"introspection_endpoint":"https://192.168.130.11:30443/mga/sps/oauth/oauth20/introspect",
"revocation_endpoint":"https://192.168.130.11:30443/mga/sps/oauth/oauth20/revoke",
"registration_endpoint":"https://192.168.130.11:30443/mga/sps/oauth/oauth20/register/ILMTIntegration",
"device_authorize_endpoint":"https://192.168.130.11:30443/mga/sps/oauth/oauth20/device_authorize",
"user_authorize_endpoint":"https://192.168.130.11:30443/mga/sps/oauth/oauth20/user_authorize",
"scopes_supported":["openid"],
"userinfo_signing_alg_values_supported":["RS256"],
"request_object_signing_alg_values_supported":["RS256"],
"token_endpoint_auth_signing_alg_values_supported":["RS256"],
"claims_parameter_supported":true ,
"request_parameter_supported":true
}
0 comments
6 views

Permalink