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:
Description | URL | Methods |
---|---|---|
OpenID Configuration | https://login.emddigital.com/.well-known/openid-configuration | GET |
JWKS Document | https://login.emddigital.com/.well-known/jwks.json | GET |
Authorization Endpoint | https://login.emddigital.com/oauth2/authorize | GET |
Token Endpoint | https://login.emddigital.com/oauth2/token | POST |
UserInfo Endpoint | https://login.emddigital.com/oauth2/userinfo | GET , 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.
The user visits your web application (or opens your native application) at https://example.com.
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
.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}The users signs in, either using username/password, a social login provider or their enterprise single-sign on provider.
The user is redirected back to your application according to the
redirect_url
. The authorization codeCODE
is attached to the redirect URL, e.g.https://example.com/idpresponse?code={CODE}&state={STATE}
.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/idpresponseThe 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 assub
,email
andemail_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.
The user visits your web application (or opens your native application) at https://example.com.
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. Thenonce
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}The users signs in, either using username/password, a social login provider or their enterprise single-sign on provider.
The user is redirected back to your application according to the
redirect_url
. The authorization codeCODE
is attached to the redirect URL, e.g.https://example.com/idpresponse?code={CODE}&state={STATE}
.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. TheAuthorization
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/idpresponseThe 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 assub
,email
andemail_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
Claim | Format | Description |
---|---|---|
alg | String | Algorithm used to sign the JWT |
kid | String | ID 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
Claim | Format | Description |
---|---|---|
aud | String, 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. |
exp | Number | "Expiration time" indicates when this token expires. The token MUST be rejected if it is expired. UNIX timestamp in seconds. |
iss | String | The issuer of the token. This value should be validated and must match https://login.emddigital.com/ |
iat | Number | "Issued At" indicates when the authentication for this token occurred. UNIX timestamp in seconds. |
email | String | Verified email address of the user. Do NOT use this value as a reliable user identifier as it is not constant, use sub instead. |
sub | String | The 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. |
ten | String | Represents 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. |
ver | Number | Indicates 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 thecontext
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.)