Enable SSO Login with Microsoft Accounts using OpenID Connect
Requirements: CloudSSO
This article details how to enable users to login to your web application using a Microsoft account. The sections below show how to configure a web Relying Party to use Microsoft Entra for SSO, and how to configure the OIDC component from CloudSSO for use in your web application to authenticate users.
OpenID Connect (OIDC) is an extension of the OAuth 2.0 protocol designed to simplify user authentication through OpenID Providers, such as Microsoft. By building on OAuth 2.0, OIDC provides a standardized way to authenticate users and access their profile information.
The OIDC component from Cloud SSO simplifies this process with a user-friendly API, making it easy to enable SSO authentication in web applications. The OIDC component abstracts much of the complexity involved in implementing OIDC, allowing developers to focus on building features rather than dealing with the intricacies of authentication protocols.
Contents
Overview
Before the OIDC component can be used to authenticate users with Microsoft Entra, an enterprise application must be created and configured in the Azure Portal. It is important to understand which grant type(s) will need to be supported by the application before creating the enterprise application.
Authorization Code | The authorization server provides an authorization code that is exchanged with a token server for an access token and an ID Token. |
Implicit | The authorization server directly provides an access token and ID token. |
Hybrid | The authorization server provides an ID Token and an authorization code. The authorization code is exchanged with the token server to obtain an access token. |
In this article, we will be configuring a Relying Party that is a web application using the authorization code grant type. The web application will be a simple website with the domain "example.com" which will allow users to authenticate using their organization's Microsoft accounts.
Creating an Enterprise Application
To get started registering your Relying Party as an application, first navigate to the Enterprise Applications node in the Azure Portal. There, select "New application" to start creating a new Relying Party.
This will lead you to the Microsoft Entra Gallery. On this page, select "Create your own application" instead of selecting one of the pre-configured applications. Once selected, a sidebar prompt will ask for the application name and how you want to register your Relying Party. The simplest way to register it as a Relying Party is to select the option to integrate the application with Microsoft Entra ID. Once you are ready, select "Create" to head to the registration page.
On the registration page, enter the required information about the application to be used by your web application (the Relying Party). If you have set up an application to use OAuth, this page will be familiar. Once you have entered the information (see the following sections for details), select "Register" and the application will be created and added to the current organization directory.
Application Registration - Account Types
First, you will be able to select the type of accounts that your Relying Party will support. The default option is to only allow accounts that are in the organization directory where the Relying Party's application is being registered. This is called a "Single tenant" application since it will work only in that directory. Another common option is to allow any account from any organization directory. This is called a "Multitenant" application and allows any account from any organizational directory to access the Relying Party. This does not include support for personal accounts without an organizational directory. Other available options allow the Relying Party's application to support all account types (personal and organizational accounts) or to restrict access to just personal accounts. The selected option is important, as it will be used later when building the discovery document URL.
Application Registration - Redirect URIs
Once the account type is selected, you can also configure a Redirect URI. When Microsoft Entra ID gets authorization from the user to allow the Relying Party to access the account, the authorization server needs to redirect the user back to the Relying Party (your web application). To ensure the user is redirected to the correct and secure location, the Relying Party must have its redirect locations registered. The simplest option is for this location to be where the OIDC component is configured to parse and validate the incoming HTTP requests. For example, the user may be redirected to "https://example.com/landing" after authorizing the Relying Party with Microsoft Entra. Redirect URIs can be added or modified later.
Application Registration - Additional Information
After the Relying Party's application has been registered, there are some optional steps to help configure the registration to better suit the application's needs. To navigate to the registration page, first head back to the "Enterprise Applications" node. You can then search for the Relying Party's name using the search bar and select the newly created application.
On this page, you can configure information such as the homepage for the Relying Party, the logo, and more through the "Properties" tab. Additionally, users and groups can be assigned to the Relying Party through the "Users and Groups" tab.
Another useful page is the "Single sign-on" page, which allows you to configure how your Relying Party will communicate with Microsoft Entra ID.
Once on the Relying Party's application page, you can configure how the Microsoft Entra ID authorization and token servers will interact with your Relying Party. On the "Authentication" page, you can add or modify redirect URIs for your Relying Party, enable implicit or hybrid flows, or modify the account types that are supported.
On the "Certificates & secrets" page, you can configure credentials that are used to identify the Relying Party to the authentication service when receiving web tokens. As a note, credentials are only allowed when the Relying Party is considered "confidential." Microsoft defines an application as confidential if it is able to securely store said credentials. A common example is a web server that hosts a Relying Party. Since users cannot access the machine directly, the Relying Party can securely store its credentials. Certificates are used to sign a JSON Web Token (JWT), which the authentication servers use to verify that the Relying Party is the one making the request. Client secrets are secret strings that the Relying Party uses to prove its identity when requesting a token, also referred to as an application password.
Other useful pages include the "Token configuration" page, which allows you to configure the tokens created by the authentication servers, and the "Branding & properties" page, which allows you to configure general information about the Relying Party.
Gathering Configuration Information
Before the OIDC component can be configured, some information must be retrieved from the enterprise application for the Relying Party. First, on the Relying Party's application page, you can find the Application (client) and Directory (tenant) IDs, which will need to be saved for later. On the same page, the "Endpoints" tab can be used to find the discovery document URL, which should also be saved.
Note that in the above example, the metadata document URL contains the "Tenant ID" in the path. This specific URL should be used if the Relying Party is single-tenant. If the Relying Party is multitenant, then the Tenant ID will be replaced with organizations.
If credentials will be used by the Relying Party, then those also will need to be provided to the component. For a certificate, you will need to provide both the private key and public key to the component. Typically, this can be a PFX file or stored in a Windows certificate store where the private key is marked as exportable.
If the client secret is being used, then the value will need to be saved for later. As a note, you can only access the value when it is first created.
With this information the OIDC component is now ready to be configured.
Setting Up and Using the OIDC Component
Before setting up the component within your web application, the following information will need to be collected from the OpenID Provider so that the OIDC component will be able to successfully build and validate requests and responses. See Gathering Configuration Information for more information.
- Discovery (Metadata) Document URL
- Client Id
- Client Secret or Certificate (Optional)
OpenID Connect Overview
The typical OpenID Connect flow works as follows. First, during the authorization step, the user is directed to the OpenID Provider's authentication page. Typically, the user will authenticate with the OpenID Provider and consent to the OpenID Provider providing their information to the Relying Party. The user is then redirected back to the Relying Party, where it will parse out the information from the incoming HTTP request.
The Relying Party will then complete the authentication step, which begins with a request to the token server. This request uses the authorization code obtained during the authorization step to prove that the user has provided consent to the token server. In response, the token server will supply the Relying Party with an Access Token, ID Token, and optional Refresh Token. Finally, to finish the authentication step, the user's ID Token is validated, and if successful, the user can be authenticated by the Relying Party.
Requesting a Discovery Document
OpenID Providers will provide information about themselves at a specific "well-known" location. When a GET request is made to this location, it will return a "discovery document" that contains information about the various endpoints required to make requests. Along with these URLs, it will also provide information about their identifier, which will be used to check the issuer claim when validating an ID Token.
After this metadata document has been retrieved, it can be cached to an accessible location for the component. The component can then load the document directly, reducing the number of requests being made by the Relying Party.
oidc.RequestDiscoveryDoc("DISCOVERY_DOC_URL");
string discoveryDocData = oidc.DiscoveryDocDetails.Content;
//Sometime later...
oidc.LoadDiscoveryDoc(discoveryDocData);
Once the metadata document has been loaded, the OIDC component is now prepared to build requests and verify responses.
Building an Authorization Request
Like the OAuth protocol that OpenID is built upon, the process typically consists of two steps. The first step is making an authorization request to the OpenID Provider, where a user will be prompted to authenticate with the provider. They will then provide consent for the OpenID Provider to share their information with the Relying Party. The user will then be redirected back to the Relying Party with proof of a successful authorization.
The OIDC component will prepare the URL to which the user must be redirected to complete authentication. Set the ClientId, ReturnURL, AuthorizationScope, State, and UsePKCE properties and then call the GetAuthorizationURL method. The sections below provide additional details on individual properties.
Redirect URI (ReturnURL)
When a user has finished the authorization step with the OpenID Provider, they need to be redirected back to the Relying Party. This HTTP redirect needs to occur at a location where the Relying Party can parse the information needed to continue the authentication process for the user. The exact location of the redirect depends on the structure of the Relying Party.
The ReturnURL property should be set to the location within the web application that will handle parsing the information from the redirect.
For example, during the setup, the example registered "https://example.com/landing" with Microsoft. The web application code at this location can use the OIDC component to parse the necessary information from the redirect. Once completed, the application will typically perform a second redirect to the page the user requested originally before logging in.
Authorization Scopes
OpenID Connect defines a set of standard authorization scopes that control what information will be available for the Relying Party to access.
Scope | Description |
openid | This is a required claim to inform the authorization server that this request intends to support the OpenID Connect standard. |
profile | This scope requests access to the user's profile claims. This includes claims such as name, family_name, picture, and more. |
This scope requests access to the email and email_verified claims. | |
address | This scope requests access to the address claim. |
phone | This scope requests access to the phone_number and phone_number_verified claims. |
offline_access | This scope requests that a refresh token be issued. |
Along with OpenID Connect claims, some authorization servers also support various OAuth scopes. This allows your Relying Party to request both OpenID Connect consent and OAuth permissions at the same time. The AuthorizationScope property should be set to the scopes required for the Relying Party to function.
State
Sometimes information needs to be retained throughout the authorization process. The state parameter can be set when building a request. The OpenID Provider will return this information when it redirects the user back to the Relying Party. For example, this is commonly used to return the user to the page from which they initiated the authentication process.
PKCE
PKCE, or Proof Key for Code Exchange, is an extension of the OAuth 2.0 protocol that is often supported by OpenID Providers. PKCE adds additional security for public applications by introducing extra parameters to both the authorization and token server requests.
A simple overview of the flow starts with the generation of a random "code verifier." This verifier is used to create a "code challenge" that is sent in the first authorization request. Then, during the request to the token server, the original "code verifier" should be included. The token server will compare the challenge that was received earlier with the verifier that was sent.
The verifier must be securely stored somewhere. Each verifier is unique to its corresponding request, meaning it should be tied to the current user session in some way.
Code Example
A user currently on the home page, "https://example.com/home", clicks the login button. The Relying Party will build the request using the OIDC component. The Relying Party will then use the OIDC component's GetAuthorizationURL method to build the request and redirect the user to the authorization endpoint.
oidc.RequestDiscoveryDoc("DISCOVERY_DOC_URL");
oidc.ClientId = "CLIENT_ID";
oidc.ReturnURL = "https://example.com/landing";
oidc.AuthorizationScope = "openid profile email";
oidc.State = "https://example.com/home";
oidc.UsePKCE = true;
string authUrl = oidc.GetAuthorizationURL();
HttpContext.Session.SetString("verifier", oidc.Config("PKCEVerifier"));
//Redirect the user's browser to authUrl
Processing an Authorization Response
Once the user has been directed to the OpenID Provider's authorization server, they will be asked to log in and provide consent for the OpenID Provider to share the requested information and permissions with the Relying Party. They will then be redirected back to the specified ReturnURL that was included in the request. This will appear as an HTTP GET request to the location specified.
Assuming the grant type is authorization code, the identity provider server will respond with an authorization code that will be used to obtain tokens from the token server. The OIDC component can parse this information from the HTTP headers of the OIDC Response by setting the OIDCResponseHeaders and OIDCResponseBody properties. Optionally, if using ASP.NET or Java Spring, this information can be automatically retrieved from the environment.
Before processing the incoming request, the OIDC component needs to be set up with certain information. Similar to the authorization request, the following should be set, requested, or loaded:
- Discovery Document
- Client Id
- Return URL
- Authorization Scopes
- Application Credentials (Optional)
- Signing Certificates
- PKCE Settings (Optional)
More details on these settings can be found below. It is important to note that these settings should match how they were configured when making the request where applicable. For example, if PKCE was used in the Authorization Request, then it should be configured to process the response.
Application Credentials
If the Relying Party has a backend component separate from the user interface (e.g., an MVC Web Application), the OAuth 2.0 protocol recommends that the Relying Party registers a secret credential alongside the application's Client Id. These credentials are provided when making a request to the token server for the Access Token, ID Token, and Refresh Token.
See Application Registration - Additional Information for details on obtaining a client secret. The simplest option offered by Microsoft is a secret value provided during the setup of the application. Microsoft also supports the use of a certificate that signs a JWT.
Signing Certificates
A part of the OpenID Connect standard involves verifying the ID Token, which is a signed JWT. Before verification can be done, the signer's public certificate must be supplied to the OIDC component. Typically, these certificates are hosted at a specific location and can be requested periodically. The location can be found through the "jwks_uri" field in the discovery document.
The OIDC component can request the signer's public certificates using the RequestSignerCerts method. This method will make a request to the "jwks_uri" defined in the discovery document to obtain a JSON Web Key Set (JWKS) with one or more public keys used to verify the signed JWT. If the JWKS is missing when they are needed, the component will automatically retrieve it, but it can be useful to cache this value to reduce the frequency of downloads. The keys can be reloaded using the LoadSignerJWKS method.
oidc.RequestDiscoveryDoc("DISCOVERY_DOC_URL");
oidc.RequestSignerCerts();
string signerJWKS = oidc.SignerJWKS;
//Sometime later...
oidc.LoadSignerJWKS(signerJWKS);
PKCE Verifier
If PKCE was used during the authorization step, the verifier must be provided for the authentication step. The PKCE verifier should have been securely stored during the authorization step and must be loaded back into the OIDC component by setting the PKCEVerifier configuration setting before processing the HTTP request.
Code Example
With everything set up, the OIDC component can successfully parse the information from the authorization response, request tokens from the token server, and validate the ID Token for the user. In this example, the HttpContext object is passed to the OIDC constructor so that the component can read the request headers and body from the HTTP context object in ASP.NET Core. Alternatively, the OIDCResponseHeaders and OIDCResponseBody properties can be set to manually pass the information to the component.
OIDC oidc = new OIDC(HttpContext);
oidc.RequestDiscoveryDoc("DISCOVERY_DOC_URL");
oidc.ClientId = "CLIENT_ID";
oidc.ClientSecret = "CLIENT_SECRET";
oidc.ReturnURL = "https://example.com/landing";
oidc.UsePKCE = true;
oidc.Config("PKCEVerifier=" + HttpContext.Session.GetString("verifier"));
oidc.ProcessOIDCResponse();
string accessToken = oidc.AccessToken;
string idToken = oidc.IdTokenInfo.IdTokenContent;
string refreshToken = oidc.RefreshToken;
string state = oidc.State;
Once the HTTP request that contains the OIDC response has been processed, the component will have obtained an access token along with the validated ID Token. This access token is a standard token produced by the OAuth 2.0 protocol and can be used to make requests to the UserInfo endpoint or other supported APIs depending on the scopes specified. Additionally, Microsoft may optionally provide a refresh token if the offline_access scope is present. This refresh token allows the application to obtain new access tokens and ID Tokens after the current ones expire, reducing the need for the user to authenticate directly with Microsoft.
Parse vs. Process
The simplest way to use the OIDC component is by utilizing the ProcessOIDCResponse method. This method handles multiple steps of the OIDC flow at once, which are typically performed sequentially. However, the component also offers more granular control over the flow if needed. The ProcessOIDCResponse method effectively encompasses the following methods:
- ParseOIDCResponse
- RequestTokens
- ValidateIdToken
- RequestSignerCerts
- ParseIdToken
Note that RequestTokens is only used when the grant type is authorization code or hybrid, and RequestSignerCerts is only used if the certificates have not been loaded yet.
ID Token Validation
Whether using the ProcessOIDCResponse or ValidateIdToken methods, the component will validate the ID Tokens in the same manner. Initially, the signature of the JWT is verified using the signing keys provided by Microsoft. If the signature is valid, the iss (issuer) claim is checked to ensure it matches the value from the discovery document. Next, the aud (audience) claim is verified to match the Client Id of the Relying Party. The final checks include time-based claims such as iat (Issued At), exp (Expiration), and nbf (Not Before).
If the ID Token fails validation, the component will throw an exception with a specific code and description indicating the reason for the failure. Certain checks can be skipped using the IdTokenVerificationFlags configuration setting.
After a token has been validated, it is safe for the component to parse the information into the IdTokenInfo and UserDetails properties. If using the ProcessOIDCResponse method this is done automatically. Otherwise, the ParseIdToken method must be used. Additionally, the GetIdTokenClaim method can be used to retrieve specific or custom claims from the token.
Getting User Information
The OpenID Connect standard provides a mechanism to retrieve information about the authenticated user via the UserInfo endpoint. This endpoint uses the access token obtained during the authorization request to authenticate the application. In response, the UserInfo endpoint will return claims about the user that can be used to enhance and customize the user experience without needing to rely solely on the ID Token.
The UserInfo endpoint is defined in the discovery document. If the OIDC component has already loaded the discovery document, call the RequestUserInfo method to obtain user information. Once the response is received from the UserInfo endpoint, the UserDetails property will be updated with the matching fields. Additionally, the GetUserInfoClaim method can be used to retrieve specific or custom claims not present in the UserDetails property.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.