Skip to main content

OpenID Connect

The EMD Digital identity provider (IdP) implements the OpenID Connect 1.0 standard. OIDC is a simple identity layer on top of the OAuth 2.0 protocol. It allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the End-User in an interoperable and REST-like manner.

Quick Start

Please get in touch with us to obtain a Client ID. The client ID is required to initiate the authentication flow. You can use the client ID NbIxOQoWfrXO_SSQofn8kQ for local development. The supported redirect URLs for this client are http://localhost:3000/auth and http://localhost:8080/auth.

tip

If your web application is built using React then please have a look at our React Integration Guide.

The EMD Digital IdP implements the Authorization Code Flow, preferably with PKCE. The PKCE flow is the recommended and most universal authorization flow that supports mobile apps, single page applications and traditional server-rendered applications and doesn't require the exchange of a shared secret.

URLs:

DescriptionURLMethods
OpenID Configurationhttps://login.emddigital.com/.well-known/openid-configurationGET
JWKS Documenthttps://login.emddigital.com/.well-known/jwks.jsonGET
Authorization Endpointhttps://login.emddigital.com/oauth2/authorizeGET
Token Endpointhttps://login.emddigital.com/oauth2/tokenPOST
UserInfo Endpointhttps://login.emddigital.com/oauth2/userinfoGET, POST

Login Flow with a Public Client

This flow does not require a client_secret and is therefore well suited for native applications and web apps. This is the preferred method, even in circumstances where a client_secret could be stored securely. Storing, rotating and securing a secret is always a challenge and should be prevented where possible.

  1. The user visits your web application (or opens your native application) at https://example.com.

  2. The application calculates a code verifier. This is a randomly chosen alphanumeric identifier with a length of at least 8 bytes. The verifier is then hashed using SHA256. The hashed value is send in the next step as the CHALLENGE.

  3. The user is redirected to the Authorization Endpoint for authentication (body and query string parameters are not URL encoded in the examples for clarity).

    GET /oauth2/authorize?
    scope=openid+email
    &response_type=code
    &client_id={CLIENT_ID}
    &redirect_uri=https://example.com/idpresponse
    &code_challenge_method=S256
    &code_challenge={CHALLENGE}
  4. The users signs in, either using username/password, a social login provider or their enterprise single-sign on provider.

  5. The user is redirected back to your application according to the redirect_url. The authorization code CODE is attached to the redirect URL, e.g. https://example.com/idpresponse?code={CODE}&state={STATE}.

  6. Your application can now exchange the CODE for an id token, access token and refresh token by calling the Token Endpoint. In a single page application, this request can be made from the browser.

    POST /oauth2/token HTTP/1.1

    Content-Type: application/x-www-form-urlencoded

    grant_type=authorization_code
    &client_id={CLIENT_ID}
    &code_verifier={VERIFIER}
    &code={CODE}
    &redirect_uri=https://example.com/idpresponse
  7. The response from the Token Endpoint is a JSON document that includes all tokens. Use the access_token to query the UserInfo Endpoint to retrieve details about the user. The response will include the OIDC claims such as sub, email and email_verified.

    GET /oauth2/userinfo HTTP/1.1

    Authorization: Bearer {access_token}
    Content-Type: application/json

Login Flow with a Confidential Client

This flow requires an application secret that you created in the app registration portal for your app. You shouldn't use the application secret in a native app or single page app because client_secrets can't be reliably stored on devices or web pages. It's required for web apps and web APIs, which have the ability to store the client_secret securely on the server side. Like all parameters discussed here, the client secret must be URL-encoded before being sent.

  1. The user visits your web application (or opens your native application) at https://example.com.

  2. The user is redirected to the Authorization Endpoint for authentication (body and query string parameters are not URL encoded in the examples for clarity). Providing a state parameter is strongly recommended to prevent CSRF. The nonce parameter is recommended to prevent replay attacks.

    GET /oauth2/authorize?
    scope=openid+email
    &response_type=code
    &client_id={CLIENT_ID}
    &redirect_uri=https://example.com/idpresponse
    &state={STATE}
    &nonce={NONCE}
  3. The users signs in, either using username/password, a social login provider or their enterprise single-sign on provider.

  4. The user is redirected back to your application according to the redirect_url. The authorization code CODE is attached to the redirect URL, e.g. https://example.com/idpresponse?code={CODE}&state={STATE}.

  5. Your application can now exchange the CODE for an id token, access token and refresh token by calling the Token Endpoint. In this code flow, the client ID has a client secret associated with it. The Authorization header is the base-64 encoded value {CLIENT_ID}:{CLIENT_SECRET} (see https://tools.ietf.org/html/rfc6749#section-2.3.1).

    POST /oauth2/token HTTP/1.1

    Authorization: Basic AAAA==
    Content-Type: application/x-www-form-urlencoded

    grant_type=authorization_code
    &client_id={CLIENT_ID}
    &code={CODE}
    &redirect_uri=https://example.com/idpresponse
  6. The response from the Token Endpoint is a JSON document that includes all tokens. Use the access_token to query the UserInfo Endpoint to retrieve details about the user. The response will include the OIDC claims such as sub, email and email_verified.

    GET /oauth2/userinfo HTTP/1.1

    Authorization: Bearer {access_token}
    Content-Type: application/json

Refresh Token

When a client acquires an access token, the client also receives a refresh token. The refresh token is used to obtain new access/refresh token pairs when the current access token expires. Refresh tokens are bound to a combination of user and client. Refresh tokens are encrypted and only the EMD Digital platform can read them.

Refresh tokens start either with the dgr_ or dgs_ prefix. The dgr_ prefix is used for refresh tokens issued to a user. The dgs_ prefix is used for refresh tokens issued to services. These prefixes allow us to efficiently search public repositories such as Github for leaked credentials and automatically revoke them.

The length and format of the refresh token is not specified. The refresh token must be stored securely.

Refresh token lifetime

Refresh tokens have a longer lifetime than access tokens. The default lifetime for the tokens is 90 days. The lifetime of the refresh token can be configured in the application settings. The EMD Digital identity platform doesn't revoke old refresh tokens when used to fetch new access tokens. Securely delete the old refresh token after acquiring a new one. Refresh tokens need to be stored safely like access tokens or application credentials.

Refresh token expiration

Refresh tokens can be revoked at any time, because of timeouts and revocations. Your app must handle rejections by the sign-in service gracefully when this occurs. This is done by sending the user to an interactive sign-in prompt to sign in again.

ID Token

An id_token is sent to the client application as part of an OpenID Connect (OIDC) flow. They can be sent along side or instead of an access token, and are used by the client to authenticate the user.

Using the ID Token

ID Tokens should be used to validate that a user is who they claim to be and get additional useful information about them - it shouldn't be used for authorization in place of an access token. The claims it provides can be used for UX inside your application, as keys in a database, and providing access to the client application.

Claims in an ID Token

An id_token is a JWT (JSON Web Token), meaning it consists of a header, payload, and signature portion. You can use the header and signature to verify the authenticity of the token, while the payload contains the information about the user requested by your client.

Header Claims

ClaimFormatDescription
algStringAlgorithm used to sign the JWT
kidStringID of the key used to sign the JWT. The key material can be retrived from the JWKS document.

Example:

{
"kid": "urzs70rYdUKWWukR2Vvtdg",
"alg": "RS256"
}

Payload Claims

ClaimFormatDescription
audString, String[]Identifies the intended recipient or recipients of the token. In an id_token, the audience is your app's Client ID. Your app should validate this value, and reject the token if the value does not match. The aud claim can be a list of strings in case the token targets multiple Client IDs.
expNumber"Expiration time" indicates when this token expires. The token MUST be rejected if it is expired. UNIX timestamp in seconds.
issStringThe issuer of the token. This value should be validated and must match https://login.emddigital.com/
iatNumber"Issued At" indicates when the authentication for this token occurred. UNIX timestamp in seconds.
emailStringVerified email address of the user. Do NOT use this value as a reliable user identifier as it is not constant, use sub instead.
subStringThe principal about which the token asserts information, such as the user of an app. This value is immutable and cannot be reassigned or reused. The subject is a pairwise identifier - it is unique to a particular Client ID. If a single user signs into two different apps using two different client IDs, those apps will receive two different values for the subject claim.
tenStringRepresents the tenant that the user is from. A tenant ID is assigned to a group of users that have a verified domain or use single sign on with their own identity provider. The tenant ID can change over time as a user is added to or removed from a tenant. It should therefore not be stored by the client.
verNumberIndicates the version of the token.

Example:

{
"exp": 1629856520,
"ten": "VTS9b-R62ADwhO7TXxw3Jg",
"ver": 1,
"aud": "1wNqoX3vBJE7STuNLhLl9A",
"sub": "LtYYdAhWnaDREp1kvv-JTA",
"iss": "https://login.emddigital.com/",
"use": "access",
"iat": 1629813320,
"jti": "jyeONeWj6hK4d_83LCzLMw"
}

Using claims to reliably identify a user

When identifying a user (say, looking them up in a database, or deciding what permissions they have), it's critical to use information that will remain constant and unique across time. Legacy applications sometimes use field like the email address, a phone number or username. All of these can change over time, and can also be reused over time - when an employee changes their name, or an employee is given an email address that matches that of a previous, no longer present employee). Thus, it is critical that your application not use human-readable data to identify a user - human readable generally means someone will read it, and want to change it.

To correctly store information per-user, use the sub claim provided by the OIDC standard. The sub claim is "pair-wise" - it is unique based on a combination of the token recipient and the Client ID. Thus, two apps that request ID tokens for a given user will receive different sub claims for that user.

Forcing the user to enter their password

login.emddigital.com will try to maintain the user session to improve the user experience and log the user in automatically. This might not be the desired behavior if the application requires the user to re-authenticate on each log in or if the application wants to allow the user to change their identity. To enable this, the identity provider supports the prompt parameter. Add the prompt=login option to the query string to the /authorize endpoint.

```
GET /oauth2/authorize?
scope=openid+email
&response_type=code
&client_id={CLIENT_ID}
&redirect_uri=https://example.com/idpresponse
&code_challenge_method=S256
&code_challenge={CHALLENGE}
&prompt=login
```

Errors

invalid_request

The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. This error code has the following error descriptions:

  • content_type The content type of the request is not valid, must be application/x-www-form-urlencoded.

  • tenant_blocked The user trying to log in has been assigned a different tenant which is provided as the tenant id in the context response key. This occurs in cases where the user is trying to use a social login provider while their email address is set up to use a organization SSO IdP.

  • redirect_uri The provided redirect_uri parameter is missing or not registered with the client ID.

  • code The code parameter is either missing, expired or already used in a previous request.

  • code_verifier The code_verifier parameter is missing, its checksum does not match the previously provided code challenge or does not have sufficient entropy (must be at least 8 bytes long).

invalid_client

Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method).

invalid_grant

The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.

unauthorized_client

The authenticated client is not authorized to use this authorization grant type.

unsupported_grant_type

The authorization grant type is not supported by the authorization server.

access_denied

The resource owner or authorization server denied the request.

unsupported_response_type

The authorization server does not support obtaining an authorization code using this method.

invalid_scope

The requested scope is invalid, unknown, or malformed.

server_error

The authorization server encountered an unexpected condition that prevented it from fulfilling the request. (This error code is needed because a 500 Internal Server Error HTTP status code cannot be returned to the client via an HTTP redirect.)

temporarily_unavailable

The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server. (This error code is needed because a 503 Service Unavailable HTTP status code cannot be returned to the client via an HTTP redirect.)