OIDCBasics

This software is not yet released and this is preliminary documentaton subject to significant change. It should not be used in production or to protect important resources at this stage.

OIDCBasics

Configuration Files

The OIDC protocol plugin will add these four new files to the system:

  • conf/sp/oidc.properties

    • Global/default OIDC options and paths to keys.

  • conf/sp/oidc-credentials.xml

    • Bean definitions of OIDC JSON Web Keys (JWK) for signing and encrypting request and response tokens. For import into the conf/sp/agents.xml file

  • conf/sp/oidc-metadata-providers.xml

    • Bean definitions of metadata providers. By default, a dynamic HTTP Provider for fetching the well-known configuration of an OpenID Provider.

  • conf/attributes/oidc-claim-rules.xml

    • OIDC transcoding rules that translate OIDC claims into internal IdPAttributes.

Enabling OIDC

There are a few steps to globally outfit your Agents with OIDC support:

  1. Install the protocol plugin (net.shibboleth.plugin.shibd.oidc).

  2. Import or generate JWK keys for OIDC use. Failure to perform this step while adding the import in the next step will prevent the SP plugin’s AgentResolverService from initializing itself.

  3. Add an import statement to the end of conf/sp/agents.xml to import all defined OIDC credentials into the configuration (but only after the previous step is complete). Noting that a single JWK can contain both private and public key material.

    ... <import resource="oidc-credentials.xml" /> </beans>
  4. Add an import statment to the end of conf/attributes/default-rules.xml to import the OIDC transcoding rules:

    ... <import resource="oidc-claim-rules.xml" /> </beans>

The system automatically does a couple of things for you when detecting the presence of the OIDC plugin:

  • Enables all the supported OIDC profiles (with default behavior) for all Agents/Applications. For the moment, this is just OIDC.SSO.

  • Defines the necessary OIDC web flow operations on behalf of the Agent to support initiating sessions and handling all the available OIDC authentication flows. For the moment, only the Authorization Code Flow is supported.

Eventually, you can control that at a much deeper level if you need to, but initially (and possibly for a long while), there is no need.

Client Registration

In vanilla (without OpenID Federation) OIDC, trust between an RP and OP is established through client registration, in which the RP becomes a registered OAuth 2.0/OIDC client with the OP. Registration occurs either statically (out-of-band) or dynamically.

Static registration involves the RP operator manually providing the OP with information about the RP, such as redirect URIs, client name, and public keys. In return, the OP generates the information the RP needs to interact with it, including the client ID and client secret.

Dynamic client registration, on the other hand, is a programmatic, runtime process that allows an RP to self-register with an OP. After registration, the RP again receives the necessary information to interact with the OP.

Currently, the plugin does not support dynamic client registration, so the RP must be manually registered with any OP you wish to use.

ClientIDs

During the client registration process, the OP issues a client identifier (client_id) and, for confidential clients, a client secret. The client_id uniquely identifies the relying party within the OP—functionally similar to how the entityID identifies a SP in SAML. Within the hub, the client_id is treated equivalently to a SAML entityID.

Issuer

The SP’s OIDC/OAuth identity (client_id) is set with an “issuer” property. You can set this globally with the sp.issuer property in conf/sp/sp.properties. If you need to get more complex and support multiple non-overlapping Agents in the Hub, you would set thep:issuer property in each Agent bean in conf/sp/agents.xml:

... <bean p:id="app.example.org" parent="shibboleth.sp.Agent" p:issuer="client_id" /> ...

Different from the SAML case, the OP is the authority for client_ids (and hence the issuer used above); you do not generate them yourself.

Authenticating Authority

In a unilateral OIDC deployment (the only kind supported until OID Federation support is added), you’ll know the specific OP you intend to use and can directly configure the OP’s issuer identifier (issuer) for an Agent. For global default assignment as in an enterprise scenario, you can set this with the sp.defaultAuthority property in conf/sp/sp.properties. If you need to override the value for a particular Agent, you would set thep:authenticatingAuthority property in the Agent bean in conf/sp/agents.xml:

... <bean p:id="app.example.org" parent="shibboleth.sp.Agent" p:authenticatingAuthority="https://op.example.org" /> ...

Client Secret and JSON Web Keys

In its simplest configuration, the RP only requires a client secret, along with the client ID, to successfully interact with an OP and receive ID tokens. After obtaining the client_secret, as described in the Client Registration section, you can globally define it using the sp.oidc.defaultClientSecret property located in conf/sp/oidc.properties. Additionally, make sure to uncomment the default client secret credential in `conf/sp/oidc-credentials.xml`.

<!-- Uncomment this bean <bean id="shibboleth.oidc.DefaultCredential" parent="shibboleth.oidc.ClientSecretCredential" p:secret="%{sp.oidc.defaultClientSecret:#{null}}"/> -->

This might be adequate for a single Agent setup relying on a single OP, but in more complicated situations involving multiple OPs, you’ll NEED to remove the default from the properties file, comment out the default secret credential, and attach different credentials to different OPs using a relying party override (see the DefiningRelyingPartyConfiguration topic for more information). An example is shown below:

<util:list id="shibboleth.sp.RelyingPartyOverrides"> <bean id="ExampleOverride" parent="RelyingPartyByName" c:relyingPartyIds="https://op.example.org"> <property name="profileConfigurations"> <util:list> <bean parent="OIDC.SSO"> <property name="clientCredential"> <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="another-secret-cooool"/> </property> </bean> </util:list> </property> </bean> </util:list>

We are developing a better method for managing client secrets.

Even with a single OP, the RP may require additional, typically asymmetric, keys for:

  • Signing Request Objects used to pass authentication request parameters to the OP.

  • Decrypting ID Tokens returned by the OP.

  • Decrypting UserInfo responses returned by the OP.

  • Authenticating itself to the OP’s Token endpoint if you are using something other than the default client‑secret–based methods (such as client_secret_basic or symmetric‑key mechanisms like client_secret_jwt). For example, using asymmetric keys to sign client authentication assertions using private_key_jwt client authentication.

The plugin supports keys in the JWK format. To configure keys, the default properties in conf/sp/oidc.properties assume your keys live in these files, though you can certainly adjust them if desired. (Note that the use of an sp subfolder is assumed.)

sp.oidc.signing.rs.key = %{idp.home}/credentials/sp/sp-signing-rs.jwk sp.oidc.signing.es.key = %{idp.home}/credentials/sp/sp-signing-es.jwk sp.oidc.encryption.rsa.key = %{idp.home}/credentials/sp/sp-encryption-rsa.jwk

You’ll need to uncomment the signing and encryption credential lists in conf/sp/oidc-credentials.xml, and the beans they reference:

<util:list id="shibboleth.oidc.SigningCredentials"> <ref bean="shibboleth.oidc.DefaultRSSigningCredential" /> <ref bean="shibboleth.oidc.DefaultESSigningCredential" /> </util:list> ... <util:list id="shibboleth.oidc.EncryptionCredentials"> <ref bean="shibboleth.oidc.DefaultRSAEncryptionCredential" /> </util:list>

If you do enable asymmetric Request Object signature signing and or ID Token / UserInfo response encryption, the RP will need to publicly publish signing and encryption public keys. This can either be achieved by registering them with the OP directly, or published in a Key Set Document. The Key Set Document endpoint is not yet supported in the alpha.

Generating New Keys

As with the IdP’s OpenID Provider plugin, keys can be generated using the provided wrappers, in bin/jwtgen.sh and bin/jwtgen.bat:

$ cd /opt/shibboleth-idp $ bin/jwtgen-sp.sh -t RSA -s 2048 -u sig -i defaultRSASign | tail -n +2 > credentials/sp/sp-signing-rs.jwk $ bin/jwtgen-sp.sh -t EC -c P-256 -u sig -i defaultECSign | tail -n +2 > credentials/sp/sp-signing-es.jwk $ bin/jwtgen-sp.sh -t RSA -s 2048 -u enc -i defaultRSAEnc | tail -n +2 > credentials/sp/sp-encryption-rsa.jwk

OIDC Metadata

Without OpenID Federation support, the mechanics of metadata handling and trust establishment in OpenID Connect differ from SAML. In OIDC, the OP publishes its own metadata and signing keys via discovery endpoints, which the RP retrieves (typically at runtime). The RP registers itself with the OP, as detailed in ClientRegistration, creating client metadata directly at the OP.

Obtaining OpenID Provider Metadata

The metadata for the OpenID Provider (OP) is retrieved dynamically using an HTTP metadata resolver. This resolver determines the Issuer location based on the Issuer Identifier. By default, it does this by appending the well-known path /.well-known/openid-configuration to the normalized Issuer Identifier (as per OpenID Connect Discovery). A default provider comes out of the box and should work without requiring any further modification.

If you do need to modify its behaviour, the configuration for the default metadata resolver can be found in /conf/sp/oidc-metadata-providers.xml. Note that the resolver configuration here differs from that for normal SAML Metadata resolvers. The maxCacheDuration defines how long the metadata remains valid before it expires and is re-fetched. Unlike SAML Metadata, the OIDC Provider metadata does not explicitly define cacheDuration or validUntil settings. An example config is given below:

<bean id="ProviderHTTPResolver" parent="shibboleth.oidc.OIDCProviderMetadataResolver"> <constructor-arg> <bean parent="shibboleth.oidc.CacheBuilder"> <constructor-arg> <bean p:cacheId="ProviderHTTPDynamicResolver" parent="shibboleth.sp.BaseProviderDynamicCacheBuilderSpec" p:minCacheDuration="PT30M" p:maxCacheDuration="PT8H" p:cleanupTaskInterval="PT30M"/> </constructor-arg> </bean> </constructor-arg> </bean>

Attributes

The primary function of this collection of software is to get data about a logged in user into an application. The Hub is responsible for producing the data from a potentially wide combination of sources and then packaging it in a simple form for the Agent to consume.

With OIDC, the primary source of that data will be:

  • The ID Token, which provides authenticated user identity and some standard claims

  • The UserInfo response, which carries additional profile information

The system automatically merges the user's identity and profile claims to generate a final set of claims for the authenticated end-user. At this stage, validation claims, such as the ID Token's audience and issuer, are remove--this process can be customized if necessary.

All data that the Hub extracts and passes to the Agent will be decoded into IdPAttribute objects with one or more values (various subtypes of IdPAttributeValue). The Agent will transform all supported types of values into strings when it receives them; the Hub maintains the fidelity of more complex types because future Agent implementations might have more advanced features for consuming the information.

The OIDC-specific transcoding rules (oidc-claim-rules.xml) include mappings for the most common OIDC claims. Currently, the ‘sub’ (identifier for the End-User at the Issuer) claim is always mapped directly to an IdPAttribute, even if no specific transcoder is defined for it.

Filtering/Acceptance

Assuming you can decode the data successfully, an additional required step is to “accept” them via the AttributeFilterConfiguration. This enables control over which data a particular authority can supply during SSO.

The IdP’s configuration (which is used by the Hub) does not default to accepting anything for security reasons. In lieu of fully tackling the filtering layer while initially testing, you can set the property sp.oidc.acceptUnfilteredAttributes in conf/sp/oidc.properties to essentially bypass use of the filter and accept “anything unspecified” that is decoded and has no explicit rule defined.

TBD example filter.

Further Reading

TBD.