Development and Testing

Certified Libraries

Visit https://openid.net/developers/certified/

Testing

Test-IdP OIDC configuration and endpoints:
https://login.test.eduid.ch/.well-known/openid-configuration

Note that the IdP configuration of the test and production infrastructure may differ. Check the configuration page for details.

Managing Test accounts:
https://test.eduid.ch

Viewer for OIDC Attributes (Test): https://apache-oidc.switch.ch

 

Sample Relying Party Configurations

We provide a sample configuration for the apache web server using the OpenID certified module mod_auth_openidc. Our configuration targets version 2.4.11.

The documentation for the configuration can be found here.

Note the following limitations of this module:

  • Up until version 2.3.0, the only client authentication method available is client_secret
  • From version 2.3.1 on, RS256 can be used for client authentication
  • Elliptic curve based authentication methods like ES256 are not supportet (yet).

 

After a user has successfully logged in, a relying party can retrieve the claims from the ID Token as php variables in the form $_SERVER(OIDC_CLAIM_given_name).  

# Configuration tested on mod_auth_openidc version 2.4.11-1 amd64

LoadModule auth_openidc_module modules/mod_auth_openidc.so

# The redirect URI, you specified in the resource registry
OIDCRedirectURI https://example.com/protected/redirect_uri

# Set a password for crypto purposes, this is used for:
# - encryption of the (temporary) state cookie
# - encryption of cache entries, that may include the session cookie, see: OIDCCacheEncrypt and OIDCSessionType
OIDCCryptoPassphrase "Insert random string here"

# Where the OIDC Metadata can be fetched. Either
# https://login.test.eduid.ch/.well-known/openid-configuration (TEST)
# or
# https://login.eduid.ch/.well-known/openid-configuration (PROD)
OIDCProviderMetadataURL https://login.test.eduid.ch/.well-known/openid-configuration

# Example authentication method using private_key_jwt
OIDCPublicKeyFiles /path/to/pub_rsa.pem
OIDCPrivateKeyFiles /path/to/priv_rsa.pem
OIDCProviderTokenEndpointAuth private_key_jwt

# Only the authorization code flow is supported
OIDCResponseType "code"

# The client ID you specified in the resource registry
OIDCClientID my_unique_client_id

# The scopes you need
OIDCScope "openid email profile offline_access swissEduIDBase"


# OIDC location. All pages https://example.com/protected/* will require a valid ID token.
<Location /protected>
  AuthType openid-connect
  Require valid-user
</Location>

Despite the fact that the Spring Boot Security OAuth 2.0 Client is not OpenID Certified, we provide a sample configuration hereafter. 

The main configuration of of the OIDC Client can be set in /src/main/resources/application.yml:

spring:
  security:
    oauth2:
      client:
        registration:
          eduid:
            client-id: my_unique_client_id
            scope: openid,profile,email
            authorization-grant-type: authorization_code
            redirect-uri: https://example.com/protected/redirect_uri
            client-authentication-method: private_key_jwt
            client-name: my_client_name
        provider:
          eduid:
            # Either
            # https://login.test.eduid.ch/ (TEST)
            # or
            # https://login.eduid.ch/ (PROD)
            issuer-uri: https://login.test.eduid.ch/

In order to apply the OIDC configuration, a Bean of type SecurityFilterChain has to be defined. 

When using the private_key_jwt client authentication method, a custom 

@Bean
public SecurityFilterChain oAuthFilterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/", "/error").permitAll()
            .anyRequest().authenticated()
            .and()
            .logout().logoutSuccessUrl("/")
            .and()
            .logout().logoutSuccessHandler(oidcLogoutSuccessHandler())
            .and()
            .oauth2Login().tokenEndpoint().accessTokenResponseClient(tokenResponseClient());

    return http.build();
}

DefaultAuthorizationCodeTokenResponseClient tokenResponseClient() {

    Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
        if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {

            // Use an RSA key for Client Authentication
            return new RSAKey.Builder(getRSAPublicKey())
                    .privateKey(getRSAPrivateKey())
                    .keyID("my_unique_rsa_key_ID") // The key ID you specified in the resource registry
                    .build();

            // Use an EC key for Client Authentication
            return new ECKey.Builder(Curve.P_256, getECPublicKey())
                    .privateKey(getECPrivateKey())
                    .keyID("my_unique_ec_key_ID") // The key ID you specified in the resource registry
                    .build();

        }
        return null;
    };

    // Apply the JWK resolver to the TokenResponseClient
    OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
            new OAuth2AuthorizationCodeGrantRequestEntityConverter();
    requestEntityConverter.addParametersConverter(
            new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));

    DefaultAuthorizationCodeTokenResponseClient tokenResponseClient =
            new DefaultAuthorizationCodeTokenResponseClient();
    tokenResponseClient.setRequestEntityConverter(requestEntityConverter);

    return tokenResponseClient;
}

The RSA and EC key pairs can be generated directly in the Java application. Alternatively, keys generated as described in Client Credentials can be imported as follows:

# First, we need to convert the keys to the DER format, such that Java can read it
#
# Convert the private key to PKCS#8 format:
openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der -nocrypt

# Generate public key in DER format:
openssl rsa -in private_key.pem -pubout -outform DER -out public_key.der
public RSAPublicKey getRSAPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
    byte[] keyBytes = Files.readAllBytes(Paths.get("/path/to/public_rsa.der"));
    X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    return (RSAPublicKey) kf.generatePublic(spec);
}

public RSAPrivateKey getRSAPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
    byte[] keyBytes = Files.readAllBytes(Paths.get("/path/to/private_rsa.der"));
    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    return (RSAPrivateKey) kf.generatePrivate(spec);
}
# We need to convert the key pair to the DER format such that Java can read it
#
# Convert private key
openssl ec -in priv_es.pem -outform DER -out priv_es.der

# Convert public key
openssl ec -in priv_es.pem -outform DER -pubout -out pub_es.der
public ECPublicKey getECPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
    byte[] keyBytes = Files.readAllBytes(Paths.get("/path/to/pub_ec.der"));
    X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("EC");
    return (ECPublicKey) kf.generatePublic(spec);
}

public ECPrivateKey getECPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
    byte[] keyBytes = Files.readAllBytes(Paths.get("/path/to/priv_ec.der"));
    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("EC");
    return (ECPrivateKey) kf.generatePrivate(spec);
}

For Angular based Single Page Applications, angular-oauth2-oidc is a OpenID Certified library. It can be installed via npm and has PKCE enabled by default.

The configuration is straight forward as the library is well documentation and provides sample projects.

 

export const authCodeFlowConfig: AuthConfig = {
    // Url of the Identity Provider:
    // https://login.test.eduid.ch/ (TEST)
    // or
    // https://login.eduid.ch/ (PROD)
    issuer: 'https://login.test.eduid.ch/',

    // URL of the SPA to redirect the user to after login
    redirectUri: 'https://example.com/protected/redirect_uri',

    // The SPA's id. The SPA is registered with this id at the auth-server
    clientId: 'my_unique_client_id',

    // Authorization code flow
    responseType: 'code',

    // The scopes requested in the resource registry 
    scope: 'openid profile email offline_access',

    showDebugInformation: true,
};
// Then in the entry page of the protected area, the OAuth Service needs to be loaded such that
// the user is redirected to the IdP's login page

ngOnInit(): void {
    this.oauthService.configure(authCodeFlowConfig);
    this.oauthService.loadDiscoveryDocumentAndLogin();

    // Automatically load user profile
    this.oauthService.events
      .pipe(filter((e) => e.type === 'token_received'))
      .subscribe((_) => this.oauthService.loadUserProfile());
  }
}

 

Two-Step Login

The edu-ID OIDC implementation supports the use of essential id_token claim request for acr. By adding the following parameter to the OIDC authentication request, a Relying Party can enforce Two-Step Login (2FA) for users that want to access the service:

claims=%7B%22id_token%22%3A%7B%22acr%22%3A%20%7B%22essential%22%3A%20true%2C%22values%22%3A%20%5B%22https%3A%2F%2Frefeds.org%2Fprofile%2Fmfa%22%5D%7D%7D%7D

URL-encoded version of:

claims = 
{
  "id_token":
  {
    "acr": 
{ "essential": true, "values": ["https://refeds.org/profile/mfa"] } } }

Reference 5.5.1.1 Requesting the 'acr' claim

 

We provide examples extending the configurations from above:

Just enable the OIDCProviderAuthorizationEndpoint in the configuration file:

OIDCProviderAuthorizationEndpoint https://login.test.eduid.ch/idp/profile/oidc/authorize?claims=%7B%22id_token%22%3A%7B%22acr%22%3A%20%7B%22essential%22%3A%20true%2C%22values%22%3A%20%5B%22https%3A%2F%2Frefeds.org%2Fprofile%2Fmfa%22%5D%7D%7D%7D

Specify the authorization URI in addition to the issuer URI.

spring:
  security:
    oauth2:
      client:
        registration:
          eduid:
            client-id: my_unique_client_id
            scope: openid,profile,email
            authorization-grant-type: authorization_code
            redirect-uri: https://example.com/protected/redirect_uri
            client-authentication-method: private_key_jwt
            client-name: my_client_name
        provider:
          eduid:
            issuer-uri: https://login.test.eduid.ch/
            authorization-uri: https://login.test.eduid.ch/idp/profile/oidc/authorize?claims=%7B%22id_token%22%3A%7B%22acr%22%3A%20%7B%22essential%22%3A%20true%2C%22values%22%3A%20%5B%22https%3A%2F%2Frefeds.org%2Fprofile%2Fmfa%22%5D%7D%7D%7D
ngOnInit(): void {
    this.oauthService.configure(authCodeFlowConfig);
    this.oauthService.loadDiscoveryDocument();

    // Need to adjust the login URL after loading the Discovery Document (.well-known/openid-configuration)
    this.oauthService.loginUrl = 'https://login.test.eduid.ch/idp/profile/oidc/authorize?claims=%7B%22id_token%22%3A%7B%22acr%22%3A%20%7B%22essential%22%3A%20true%2C%22values%22%3A%20%5B%22https%3A%2F%2Frefeds.org%2Fprofile%2Fmfa%22%5D%7D%7D%7D',

    this.oauthService.initLoginFlow();

    // Automatically load user profile
    this.oauthService.events
      .pipe(filter((e) => e.type === 'token_received'))
      .subscribe((_) => this.oauthService.loadUserProfile());
  }