Criipto
  1. Getting Started
  2. Using OpenID Connect

Using OpenID Connect to integrate with Criipto Verify

Criipto Verify is an OpenID Provider. It is integrated through the authentication API which follows the OpenID Connect and OAuth 2.0 specifications.

Supported OpenID Connect Flows

The OpenID Connect(OIDC) protocol defines different strategies (flows) to authenticate users. Criipto Verify supports four different OpenID Connect flows:

1. The Authorization Code Flow

The Authorization Code Flow is used for traditional server-based web applications, also referred to as confidential clients. These applications can securely store a client secret and establish back-channel communication with the Criipto Verify service. This facilitates a secure code-for-token exchange during authentication.

2. The PKCE Flow

The PKCE Flow (pronounced pixy) can be used by public clients such as single-page applications (SPAs) and native applications that cannot keep a secret. With PKCE, a one-time secret is generated and used for the code exchange.

3. The Implicit Flow

The Implicit Flow returns a token directly in the browser. Support for this flow is being discontinued, although it will continue to function on test domains for the foreseeable future for simpler debugging during development.

4. The CIBA flow

The CIBA flow is designed for use cases where the client application isn't directly operated by the end-user but can trigger the authentication process on their behalf. A canonical example is a call center agent verifying a caller's identity.

The choice of the OpenID Connect flow depends on the requirements and architecture of the client application implementing the OIDC authentication.

The following sections describe the four flows and introduce the parameters to configure the authentication and subsequent user information retrieval.

Please beware that you don't have to go through the below motions manually. Most often it will be handled by configuring an OpenID Connect package on your platform of choice.

Authenticate the User

To begin the login flow, you will need to authenticate the user at the identity source indicated in your request.

To authenticate the user, your app must send the user to the OAuth2 authorization endpoint (/oauth2/authorize) with the appropriate set of parameters.

You can find the URL for the OAuth2 authorization endpoint in the OpenID Connect Discovery Document exposed on your Criipto Verify Domain:

GET https://YOUR_SUBDOMAIN.criipto.id/.well-known/openid-configuration

The response from this endpoint is a JSON document, with an authorization_endpoint property. The corresponding property value is the URL of the OAuth2 authorization endpoint.

Example authorization URL

The following initiates the authentication through an OAuth2 authorization request:

GET https://YOUR_SUBDOMAIN.criipto.id/oauth2/authorize?
    response_type=code|id_token&
    client_id=CLIENT_ID&
    redirect_uri=YOUR_RETURN_URL&
    acr_values=CHOSEN_IDENTITY_SERVICE&
    scope=openid&
    state=YOUR_STATE

Note that providing response_type=code specifies that you want either the traditional back-channel Authorization Code Flow or the PKCE Flow. If you specify response_type=id_token you indicate that you want the Implicit Flow. In the Implicit Flow, you receive the issued token in a URL fragment on the return URL.

If you want to receive the response in another way you must specify the response_mode parameter, see below.

Parameters

Parameter nameDescription
response_typeDenotes the kind of credential that Criipto will return (code or id_token). If you are integrating a traditional server based web application (back-channel flow) or a PKCE-enabled client, use code. Use id_token for legacy single page applications using a front-channel flow.
client_idYour application's Client ID. You can find this value in the Criipto Verify UI in the settings for actual application.
redirect_uriThe URL to which Criipto will redirect the browser after authentication has been completed. The authorization code and the id_token will be available in the code and id_token URL parameter for the back-channel flow and on a URL query parameter for the front-channel flow. This URL must be pre-registered as a valid callback URL in your application settings.

Warning: Per the OAuth 2.0 Specification, Criipto removes everything after the hash and does not honor any fragments.
scopeFor applications configured with a static scope strategy, specify openid. This gets you the information configured in the management dashboard for each kind of eID (where applicable).

For applications configured with a dynamic scope strategy, you must specify scope tokens for the types of data you want, in addition to the openid scope token. Possible values are described in the individual eID articles.

You can read more about this parameter here
acr_valuesIdentifies which eID identity service you want to use. You can only specify one value, and it must identify the exact type of identity service, as some countries have, for example, both a mobile and web based service. Possible values can be found in the authorize request builder.
response_mode(optional) Specifies how you want your result delivered via the redirect_uri: Use query to return the code/id_token as a query parameter, fragment to have it delivered on a URL fragment, and finally form_post to have it posted back to your redirect_uri.

Default values are query for response_type=code and fragment for response_type=id_token.
state(optional but recommended) An opaque arbitrary alphanumeric string your app adds to the initial request that Criipto includes when redirecting back to your application.
login_hint(optional) Various use cases leverage this parameter. You can read more about them here.
prompt(optional) Specifies whether the user will be forced to re-authenticate. Possible values are none, login, consent, and consent_revoke. More information is available in our SSO guide and in our authorize URL builder.

When adding login to your application, you will build the authorization URL (also called the authorize URL) and redirect the user to it. An HTML snippet for your authorize URL might look like this:

<a href="https://acme-corp.criipto.id/oauth2/authorize?
  response_type=id_token&
  client_id=urn:debug:jwt.io&
  acr_values=urn:grn:authn:no:bankid&
  redirect_uri=https://jwt.io&
  scope=openid&
  state=etats">
  Sign in with Norwegian BankID
</a>

You can try the above URL right now if you have a test user for Norwegian BankID.

Your next steps will depend on the OpenID Connect flow type you're implementing. See the relevant section below for a description of how each flow unfolds:

After completing the steps specific to your chosen flow, you can proceed to validating the JWT received from successful authentication.

Authenticate with back-channel Authorization Code Flow

Example request

GET https://YOUR_SUBDOMAIN.criipto.id/oauth2/authorize?
    response_type=code&
    response_mode=query&
    client_id=CLIENT_ID&
    redirect_uri=YOUR_RETURN_URL&
    acr_values=CHOSEN_IDENTITY_SERVICE&
    scope=openid&
    state=YOUR_STATE

Example response

For the Authorization Code Flow, when you used response_type=code, you will receive an HTTP 302 response which redirects your browser to your specified redirect_uri with the authorization code included at the end of the URL:

HTTP/1.1 302 Found
Location: YOUR_RETURN_URL?code=AUTHORIZATION_CODE&state=YOUR_STATE

Error response

In case the authentication request fails, you will receive an HTTP 302 response which redirects your browser to your specified redirect_uri with error included at the end of the URL:

HTTP/1.1 302 Found
Location: YOUR_RETURN_URL?error=ERROR_CODE&error_description=...&state=YOUR_STATE

Exchange the code for a token

For the Authorization Code Flow, you will need to exchange the returned code for an actual token. This is done by posting the authorization code received from the previous step to the token endpoint.

For PKCE-enabled clients, this exchange is based on a one-time secret created by the OIDC library you use to handle the flow, and the exchange will also be handled by the same library.

For traditional back-channel flows, note that you must use a HTML-form-style HTTP POST here, and preferably send the credentials in the Authorization HTTP header. You must also x-www-form-urlencode the values of the CLIENT_ID and CLIENT_SECRET, respectively, before constructing the Authorization header in Basic format.

HTTP POST https://YOUR_SUBDOMAIN.criipto.id/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic BASE64(xWwwFormUrlEncode(CLIENT_ID):xWwwFormUrlEncode(CLIENT_SECRET))

grant_type=authorization_code&code=AUTHORIZATION_CODE&client_id=CLIENT_ID&redirect_uri=YOUR_RETURN_URL

See more code exchange authorization examples in various languages.

Note We do also support receiving the client credentials in the payload, but this usage is discouraged by the OAuth2 specification, and we strongly recommend that you send the credentials in the Authorization: Basic ... HTTP header value as described above.

The client id and secret are retrieved from the Criipto Verify management UI and the redirect_uri must be exactly the same you used in the authorization request in the previous step.

Note that the back-channel exchange of the authorization code requires the use of the client secret, which is basically just a password, and therefore must always be made via a back-channel - server to server - and never from a public client like a browser or native application. Never include the secret in the frontend code.

For PKCE-enabled clients, the secret is generated on-the-fly, and no special handling of it is required by you.

Authenticate with PKCE

PKCE allows you to use one-time secrets to perform code exchange.

@criipto/auth-js and @criipto/verify-react support PKCE.

Example request

GET https://YOUR_SUBDOMAIN.criipto.id/oauth2/authorize?
    response_type=code&
    response_mode=query&
    client_id=CLIENT_ID&
    redirect_uri=YOUR_RETURN_URL&
    acr_values=CHOSEN_IDENTITY_SERVICE&
    scope=openid&
    state=YOUR_STATE&
    code_challenge=YOUR_CODE_CHALLENGE&
    code_challenge_method=S256

Example response

You will receive an HTTP 302 response which redirects your browser to your specified redirect_uri with the authorization code included at the end of the URL:

HTTP/1.1 302 Found
Location: YOUR_RETURN_URL?code=AUTHORIZATION_CODE&state=YOUR_STATE

Code exchange

HTTP POST https://YOUR_SUBDOMAIN.criipto.id/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=AUTHORIZATION_CODE&client_id=CLIENT_ID&redirect_uri=YOUR_RETURN_URL&code_verifier=YOUR_CODE_VERIFIER

Authenticate with Implicit Flow

Implicit Flow, which returns an id_token directly in the browser via the # fragment, is supported, but not recommended.

@criipto/auth-js supports Implicit Flow.

Example request

GET https://YOUR_SUBDOMAIN.criipto.id/oauth2/authorize?
    response_type=id_token&
    response_mode=fragment&
    client_id=CLIENT_ID&
    redirect_uri=YOUR_RETURN_URL&
    acr_values=CHOSEN_IDENTITY_SERVICE&
    scope=openid&
    state=YOUR_STATE

Example response

HTTP/1.1 302 Found
Location: YOUR_RETURN_URL#id_token=eyJ[...].eyJ[...].Sfl[...]&state=[...]

Authenticate with CIBA

Client-Initiated Backchannel Authentication (CIBA) is a new authentication flow where the client application initiates the authentication process on behalf of the end-user.

The caller provides a hint that triggers an authentication request to the user's authentication device, typically a phone. The user can then approve the request and complete the authentication process as usual.

CIBA is an extension to OpenID Connect. However, unlike in other OpenID Connect flows, there is a direct communication between your application and the OpenID Provider (Criipto Verify), without redirects through the user's browser.

CIBA supports use cases not covered by other OpenID Connect flows, such as:

  • Confirming user's identity to a call center agent during a phone call or via a chat.
  • Using a smartphone to authorize a payment at a point of sale terminal.
  • Enabling a bank agent to authenticate a customer in a bank branch during a face-to-face interaction.

Criipto Verify supports CIBA for the Swedish BankID Phone Authentication.

Example request

The client application shall make an HTTP POST request to the backchannel authentication endpoint to ask for end-user authentication. The CIBA flow is only available for confidential clients, and the example below uses private key JWTs for client authentication.

HTTP POST https://YOUR_SUBDOMAIN.criipto.id/ciba/bc-authorize
Content-Type: application/x-www-form-urlencoded

scope=openid
&callInitiator=RP
&login_hint=sub:ssn:SSN
&acr_values=urn:grn:authn:se:bankid
&binding_message=BINDING_MESSAGE
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion=YOUR_JWT_ASSERTION

Example response

Successful response will contain the request id, auth_req_id:

{ 
  "auth_req_id" : "3857f8ff-21b9-48ae-a732-a3bd8128a7ae",
  "expires_in" : 120 
}

Obtaining the token

Poll the token endpoint (/oauth2/token) providing the auth_req_id:

HTTP POST https://YOUR_DOMAIN.criipto.id/oauth2/token
Content-Type: application/x-www-form-urlencoded

auth_req_id=AUTH_REQ_ID
&grant_type=urn:openid:params:grant-type:ciba
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion=$client_assertion

Token response

The token will be issued upon successful user identification:

{
  token_type: 'Bearer',
  expires_in: '120',
  id_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjgyN0Q5QTNFOTg2MTY0OTVBQzZGRTE3MUFFNkRBM0IzQ0ExNDE5MjEifQ.eyJpc3MiOiJodHRwczovL25hdGFsaWEtZGV2LXRlc3QuY3JpaXB0by5pbyIsImF1ZCI6InVybjpjaWJhIiwiaWRlbnRpdHlzY2hlbWUiOiJzZWJhbmtpZCIsImF1dGhlbnRpY2F0aW9udHlwZSI6InVybjpncm46YXV0aG46c2U6YmFua2lkIiwiYXV0aGVudGljYXRpb25tZXRob2QiOiJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpTb2Z0d2FyZVBLSSIsImF1dGhlbnRpY2F0aW9uaW5zdGFudCI6IjIwMjQtMDUtMjJUMTA6NDE6NDEuMzYxWiIsIm5hbWVpZGVudGlmaWVyIjoiYmI5YzIzNjRkZmFlNDRmM2JjZmQ5OTkwNTNkOTRmOWUiLCJzdWIiOiJ7YmI5YzIzNjQtZGZhZS00NGYzLWJjZmQtOTk5MDUzZDk0ZjllfSIsInNlc3Npb25pbmRleCI6ImI2NWYxMWI2LWViYTctNDg4Mi05MDBhLTVmMmQ5M2EzOGRiYSIsInNzbiI6IjE5NjgwMjAyMDU3NSIsIm5hbWUiOiJUZXJuZSBQYXVsc2VuIiwiZ2l2ZW5uYW1lIjoiVGVybmUiLCJnaXZlbl9uYW1lIjoiVGVybmUiLCJzdXJuYW1lIjoiUGF1bHNlbiIsImZhbWlseV9uYW1lIjoiUGF1bHNlbiIsImlwYWRkcmVzcyI6IjE4NS4xNTcuMTM0LjEzNCIsImNvdW50cnkiOiJTRSIsImlhdCI6MTcxNjM3NDUwMSwibmJmIjoxNzE2Mzc0NTAxLCJleHAiOjE3MTYzNzU3MDF9.RVQnlukfoH597uXzE1Gays5DElGzAr8xgOmi7ZWppaL3QPGhV4vK2o6qLhxXg_-FKG9xCwHR6gEhnNzWA3W3B6Q2zJeQTYh9okUvTmmhAFIyDL7lEtfWVVKUKvauDisYVZDjAxJQS_1zbgPEi5I-UJ6_kvMGH-wC13MAD2bZGTGR2dR-ZevBUn7plOt0PKXrIZD3vwxDfebTMPQqX_9SNT5F7GLjCcpeVK-T5LOgmUMFcTAbHvNyklqP5ymRHsZLDw_ib4I7ZqODhR-3uISWo1NvG4Y84iBcqv50WRNlmMUm004LfPw1flM5DNsVyUWCqYW8m7eBEwLp5va-6OQG4w',
  access_token: 'cf1ce646-7fbe-4740-9c56-fe3f0891f6c6'
}

Validate the JWT

You can now proceed with validating the JWT returned in response and access the end-user information it contains. The validation step is required; without it, you cannot trust the contained end-user information.

We strongly recommend that you find a battle-hardened library for your specific platform to do this heavy lifting. You can find an extensive list of libraries on jwt.io. Alternatively, Criipto provides a list of integrations that handle most of the authentication process, including JWT validation.

For more informaiton, see OpenID Connect security best practices and JWT validation guide.

The 'scope' parameter

You can use the scope query parameter to specify which user data you want on a per-authorize request basis.

You must explicitly enable this feature per application. By default, Criipto Verify uses the configured settings per eID (thus effectively ignoring any other scope values than openid). Activate the "Enable dynamic scopes" toggle on an application to start using this feature. Once you do that, Criipto Verify will switch from using the per eID configured scope options to the ones you specify in the authorize request.

Anatomy of the scope value

The scope query parameter can contain multiple values. Each value is separated by a single blank character (' ' / ASCII 32 / Unicode U+0020 ).

The scope value must always contain the value openid, and it may also contain any of the following additional values:

  • address
  • email
  • phone
  • birthdate
  • ssn

You can see which values are supported for the various eIDs in the authorize URL builder or in the individual eID articles.

The quirks-mode variant of sending the same instructions via the login_hint is fully supported. Consult the authorize URL builder for details.

The 'login_hint' parameter

Just as for the scope parameter, the login_hint parameter can contain multiple values. Each value here must also be separated by a single blank character.

Criipto Verify supports controlling the runtime behavior in quite a few aspects by values sent in this parameter. We chose this approach as it is most often possible to send them through intermediaries if your architecture has such components (other OpenID Providers such as Azure AD or Auth0).

Also, we use it as a fallback for cases where intermediaries do not let you pass values via otherwise standardized OpenID Connect query parameters (most notably scope and acr_values).

Go to these specific use cases where login_hint is used to specify behaviour or get around limitations:

Action specifiers

For DK and the SE another-device flows, you can add the following values to the login_hint query parameter to change the default wording used during login/signing.

  • action:confirm
  • action:accept
  • action:approve
  • action:sign
  • action:login (the default, mostly present for completeness)

Sending, say, action:approve will change "Login at ..." worded elements to "Approve at ...".

Authorize URL builder example

End-user confirmation texts (message parameter)

Note that specifically for Danish MitID and Swedish BankID, you may also send a base64-url-encoded message via message:BASE64URL(...text...), which will be shown to the end user in the app. For example, if you want to show a Transfer EUR 100 to IBAN DK123456781234 message to the user, add a message:VHJhbnNmZXIgRVVSIDEwMCB0byBJQkFOIERLMTIzNDU2NzgxMjM0 value to the login_hint.

Danish MitID: Authorize URL builder example

The maximum length of the message for Danish MitID is 130 characters before base64 encoding (according to our tests).

Swedish BankID: Authorize URL builder example

The maximum length of the message for Swedish BankID is 1500 characters after base64 encoding.

Code exchange authorization examples

# PHP
"Authorization" => "Basic ".base64_encode(urlencode(CRIIPTO_CLIENT_ID).":".urlencode(CRIIPTO_SECRET))
// Node.js
'Authorization': "Basic " + Buffer.from(`${encodeURIComponent(CRIIPTO_CLIENT_ID)}:${encodeURIComponent(CRIIPTO_SECRET)}`).toString('base64')

// Javascript
'Authorization': "Basic " + btoa(`${encodeURIComponent(CRIIPTO_CLIENT_ID)}:${encodeURIComponent(CRIIPTO_SECRET)}`)
// C#
using System;
using System.Text;
"Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(Uri.EscapeDataString(CRIIPTO_CLIENT_ID) + ":" + Uri.EscapeDataString(CRIIPTO_SECRET)))

// C# - if you have a dependency on System.Web.dll or you are willing to take it
using System;
using System.Text;
using System.Web.Security.AntiXss;
"Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(AntiXssEncoder.UrlEncode(CRIIPTO_CLIENT_ID) + ":" + AntiXssEncoder.UrlEncode(CRIIPTO_SECRET)))