How to implement OpenID Connect eKYC / Identity Assurance

Providers of verified identities are encouraged to adopt the new OpenID Connect extension for eKYC and Identity Assurance (eKYC / IdA). This guide explains how to configure your Connect2id server (v9.0+) and integrations for that. It assumes draft 11 of the specification.

1. Configuration

1.1 OpenID provider metadata

Configure the Connect2id server to advertise support for the eKYC / IdA extension, such as the available trust frameworks and the list of verified claims which can be requested by Relying Parties (RPs). This will also enable processing of verified claims requests in the authorisation session API of the Connect2id server.

Example configuration for eIDAS at level of assurance "High":

op.assurance.supportsVerifiedClaims=true
op.assurance.supportedTrustFrameworks=eidas_ial_high
op.assurance.supportedIdentityEvidenceTypes=qes
op.assurance.supportedVerifiedClaims=name,given_name,family_name,birthdate

When the end-user consent is submitted to the Connect2id server the default action is to persist it (long_lived=true), so that the next time the end-user is asked to authorise access the IdP UI can indicate which of the requested claims were already allowed and which are being asked for for the first time. If all requested claims have already been granted a consent prompt will not come up.

Because requests for verified claims include metadata, to indicate the wanted trust framework or some other verification parameter, the Connect2id server should be configured to always prompt for consent, so that the authorisation handler has a chance to examine that metadata also in those cases when all requested claims have been previously allowed:

op.authz.alwaysPromptForConsent=false

If the consent is not going to be persisted, by setting long_lived=false of the consent object, the consent prompt will always come up and the above configuration is not necessary.

2. Integration

How to update your authorisation session handler for verified claims:

  • Claims that are requested as verified by an RP will appear with verified: prefixed to their name in the usual consent prompt.

    For example, the following claims request

    {
     "userinfo" : {
        "verified_claims" : {
           "claims" : {
              "given_name"  : null,
              "family_name" : null,
              "birthdate"   : null
           }
        }
     }
    }
    

    will translate to a consent prompt similar to this:

    {
    "type"        : "consent",
    "sid"         : "g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE",
    "display"     : "popup",
    "sub_session" : { ... },
    "client"      : { ... },
    "scope"       : { "new"       : [ "openid" ],
                      "consented" : [ ] },
    "claims"      : { "new"       : { "essential" : [ ],
                                      "voluntary" : [ "verified:given_name",
                                                      "verified:family_name",
                                                      "verified:birthdate" ] },
                      "consented" : { "essential" : [ ],
                                      "voluntary" : [ ] } }
    }
    
  • The handler must check at least if a particular trust framework is requested, to ensure it is actually supported by the IdP. This will appear in the verification metadata. If the handler is capable it can also check for other optional requested verification parameters, such as the identity proofing evidences.

    Claims request by an RP according to the trust framework eIDAS at the identification assurance level "High":

    {
     "userinfo" : {
        "verified_claims" : {
           "verification" : {
              "trust_framework" : "eidas_ial_high"
           },
           "claims" : {
              "given_name"  : null,
              "family_name" : null,
              "birthdate"   : null
           }
        }
     }
    }
    

    The verification metadata will appear in the consent prompt under claims.verification:

    {
    "type"        : "consent",
    "sid"         : "g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE",
    "display"     : "popup",
    "sub_session" : { ... },
    "client"      : { ... },
    "scope"       : { "new"         : [ "openid" ],
                      "consented"   : [ ] },
    "claims"      : { "new"         : { "essential" : [ ],
                                        "voluntary" : [ "verified:given_name",
                                                        "verified:family_name",
                                                        "verified:birthdate" ] },
                      "consented"   : { "essential" : [ ],
                                        "voluntary" : [ ] },
                      "verification : { "userinfo" : { "trust_framework" : "eidas_ial_high" } } }
    }
    
  • If the RP has set the purpose parameter to explain why verification is being requested, the provider should display the message in the consent screen to the end-user.

    {
    "type"        : "consent",
    "sid"         : "g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE",
    "display"     : "popup",
    "sub_session" : { ... },
    "client"      : { ... },
    "scope"       : { "new"       : [ "openid" ],
                      "consented" : [ ] },
    "claims"      : { "new"       : { "essential" : [ ],
                                      "voluntary" : [ "verified:given_name",
                                                      "verified:family_name",
                                                      "verified:birthdate" ] },
                      "consented" : { "essential" : [ ],
                                      "voluntary" : [ ] } },
    "purpose"     : "Required details for the insurance contract"
    }
    

    The RP can also attach a purpose message to each individual requested claim. The purpose strings will then appear in a the claims.purposes JSON object with claim name / purpose string pairs.

    Important: In order to prevent injection attacks all special characters in a purpose string must be escaped before shown in a user interface!

  • If you need access to the raw claims request in the consent prompt enable op.authz.includeRawClaimsRequestInPrompt.

    Alternatively, make a GET call for the authorisation session which will also return the OpenID authentication request and its "claims" parameter.

  • The consented verified claims are to be submitted by the handler in the usual claims parameter of the consent object. The name of each verified claims must be prefixed with verified: so that the claims source can distinguish the verified claims from the regular ones when composing the UserInfo object.

    Refresh tokens should not be issued, unless the IdP needs to give the relying party long-term access to the verified claims.

    Example consent for verified (name, address) and regular claims (email):

    {
    "scope"         : [ "openid" ],
    "claims"        : [ "email", "verified:name", "verified:address" ],
    "refresh_token" : { "issue" : false }
    }
    
  • Update your Connect2id server claims source to detect if the list of the consented claims includes verified ones (marked with the verified: prefix), and put them into the verified_claims element, together with the necessary verification metadata, as shown in this example for the OpenID Connect SDK.

  • If the authorisation session handler needs to pass some data to the claims source when provisioning the actual claims, for example the verification element or even claim values, it can do so via the claims_data consent parameter.

    {
    "scope"       : [ "openid" ],
    "claims"      : [ "email",
                      "verified:name",
                      "verified:address" ]
    "claims_data" : { "trust_framework"      : "eidas_ial_high",
                      "time"                 : "2020-03-16T18:25Z",
                      "verification_process" : "f24c6f-6d3f-4ec5-973e-b0d8506f3bc7" }
    }
    

    The claims_data will be included in the issued access token (if the claims are to be delivered at the UserInfo endpoint). To keep the claims data confidential from the RP choose either an identifier access token encoding (access_token.encoding = IDENTIFIER in the consent) in the consent or if a self-contained (JWT) token is chosen it must be additionally encrypted (access_token.encrypt = true).

    An AdvancedClaimsSource SPI implementation can retrieve the claims data JSON object by a call to the ClaimsSourceRequestContext.getClaimsData method.

3. Future improvements

Connect2id server improvements which are being considered to further aid processing of eKYC / IdA requests:

  • Update the consent prompt to support two or more verification objects. At present if the request includes more than one verified_claims elements only the verification of the first one will be included. The remaining can be obtained by parsing the raw claims.

  • An op.authz.includeRawClaimsRequestInAuthPrompt setting to cause the raw claims request parameter to also be included in the authentication prompt. This will allow a provider which supports multiple levels of authentication assurance (ACR / LoA) to find out which one is appropriate for a verified claims request (in the absence of an acr_values request parameter or when it doesn't match the ACR required for verified claims).

  • Implement a smart algorithm to determine when the consent prompt should come up when previous consent is present (persisted) and verified claims are being requested.