Sitecore OrderCloud Documentation

docs

Explore new docs
Portal login

Implementing Single Sign On with Azure AD B2C IDP

Published by Crhistian Ramirez-Hernandez on July 23, 2024

OpenID Connect is a powerful feature that enables you to provide single sign-on capabilities for any identity provider that supports the specification. In this tutorial we’ll walk you step by step through what you’ll need to get single sign-on working by using Azure AD B2C as the identity provider. By the end of this tutorial, you’ll be able to sign in via Azure and be logged into OrderCloud.

Final Product

Before we start, let’s understand the finished product. By the end of this tutorial you will have a locally running application that will redirect you to Azure’s sign-in page and after successfully signing in you should see your login details including:

  • Currently authenticated user
  • OrderCloud Access Token
  • OrderCloud Refresh Token (if configured)
  • Azure ID Token (if configured)

Create your Marketplace

First, you will need to create a new Marketplace.

image

After creating it, take special note of the API server, this identifies the base URL needed for all API requests.

image

For this demonstration we are on the Sandbox environment in the region Us-West so our base API URL is https://sandboxapi.ordercloud.io, yours may look different.

Create supporting entities

We will be creating a single-sign-on experience for buyer users specifically, so we’ll create the most basic OrderCloud entities required to support that scenario.

Create a buyer organization

1POST https://sandboxapi.ordercloud.io/v1/buyers HTTP/1.1
2Authorization: Bearer INSERT_ACCESS_TOKEN_HERE
3Content-Type: application/json; charset=UTF-8;
4
5{
6 "ID": "buyer1",
7 "Name": "Buyer 1",
8 "Active": true
9}
1import { Tokens, Buyers } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5Buyers.Create({
6 ID: "buyer1",
7 Name: "Buyer 1",
8 Active: true
9})
10.then(response => {
11 // returns the newly created buyer organization
12 console.log(response);
13})
14.catch(err => console.log(err));
15
16})
1import { Tokens, Buyers, Buyer, OrderCloudError } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5const buyer: Buyer = await Buyers.Create({
6 ID: "buyer1",
7 Name: "Buyer 1",
8 Active: true
9})
10.catch((err:OrderCloudError) => console.log(err));
11console.log(buyer);
1using OrderCloud.SDK;
2
3var client = new OrderCloudClient(...);
4
5Buyer response = await client.Buyers.CreateAsync(new Buyer {
6 ID = "buyer1",
7 Name = "Buyer 1",
8 Active = true
9});

Create a Security Profile

1POST https://sandboxapi.ordercloud.io/v1/securityprofiles HTTP/1.1
2Authorization: Bearer INSERT_ACCESS_TOKEN_HERE
3Content-Type: application/json; charset=UTF-8;
4
5{
6 "ID": "buyerProfile",
7 "Name": "Buyer Security Profile",
8 "Roles": ["Shopper"]
9}
1import { Tokens, SecurityProfiles } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5SecurityProfiles.Create({
6 ID: "buyerProfile",
7 Name: "Buyer Security Profile",
8 Roles: ["Shopper"],
9})
10.then(response => {
11 // returns the newly created security profile
12 console.log(response);
13})
14.catch(err => console.log(err));
15
16})
1import { Tokens, SecurityProfiles, SecurityProfile, OrderCloudError } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5const securityProfile: SecurityProfile = await SecurityProfiles.Create({
6 ID: "buyerProfile",
7 Name: "Buyer Security Profile",
8 Roles: ["Shopper"],
9})
10.catch((err:OrderCloudError) => console.log(err));
11console.log(securityProfile);
1using OrderCloud.SDK;
2
3var client = new OrderCloudClient(...);
4
5await client.AuthenticateAsync();
6
7SecurityProfile response = await client.SecurityProfiles.CreateAsync(new SecurityProfile {
8 ID = "buyerProfile",
9 Name = "Buyer Security Profile",
10 Roles = new ApiRole[] { ApiRole.Shopper }
11});

Assign the security profile to the buyer organization

1POST https://sandboxapi.ordercloud.io/v1/securityprofiles/assignments HTTP/1.1
2Authorization: Bearer INSERT_ACCESS_TOKEN_HERE
3Content-Type: application/json; charset=UTF-8;
4
5{
6 "SecurityProfileID": "buyerProfile",
7 "BuyerID": "buyer1"
8}
1import { Tokens, SecurityProfiles } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5SecurityProfiles.SaveAssignment({
6 SecurityProfileID: "buyerProfile",
7 BuyerID: "buyer1"
8})
9.then(() => {
10 // no response when security profile assigned
11})
12.catch(err => console.log(err));
13
14})
1import { Tokens, SecurityProfiles, OrderCloudError } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5await SecurityProfiles.SaveAssignment({
6 SecurityProfileID: "buyerProfile",
7 BuyerID: "buyer1"
8})
9.catch((err:OrderCloudError) => console.log(err));
1using OrderCloud.SDK;
2
3var client = new OrderCloudClient(...);
4
5await client.AuthenticateAsync();
6
7await client.SecurityProfiles.SaveAssignmentAsync(new SecurityProfileAssignment {
8 SecurityProfileID = "buyerProfile",
9 BuyerID = "buyer1"
10});

Create an API client

1POST https://sandboxapi.ordercloud.io/v1/apiclients HTTP/1.1
2Authorization: Bearer INSERT_ACCESS_TOKEN_HERE
3Content-Type: application/json; charset=UTF-8;
4
5{
6 "AccessTokenDuration": 600,
7 "Active": true,
8 "AppName": "Buyer Client",
9 "RefreshTokenDuration": 43200,
10 "AllowAnyBuyer": true,
11 "AllowSeller": true
12}
1import { Tokens, ApiClients } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5ApiClients.Create({
6 AccessTokenDuration: 600,
7 Active: true,
8 AppName: "Buyer Client",
9 RefreshTokenDuration: 43200,
10 AllowAnyBuyer: true,
11 AllowSeller: true
12})
13.then((response) => {
14 console.log(response);
15})
16.catch(err => console.log(err));
17
18})
1import { Tokens, ApiClients, ApiClient, OrderCloudError } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5const apiClient: ApiClient = await ApiClients.Create({
6 AccessTokenDuration: 600,
7 Active: true,
8 AppName: "Buyer Client",
9 RefreshTokenDuration: 43200,
10 AllowAnyBuyer: true,
11 AllowSeller: true
12})
13.catch((err:OrderCloudError) => console.log(err));
14console.log(apiClient);
1using OrderCloud.SDK;
2
3var client = new OrderCloudClient(...);
4
5await client.AuthenticateAsync();
6
7ApiClient response = await client.ApiClients.Create(new ApiClient {
8 AccessTokenDuration = 600,
9 Active = true,
10 AppName = "Buyer Client",
11 RefreshTokenDuration = 43200,
12 AllowAnyBuyer = true,
13 AllowSeller = true
14});

Make sure to record the ID from the response. You will need it when creating the OpenID Connect

Configuring OIDC (OpenID Connect) via OrderCloud

Configure Azure

Create an Azure AD B2C tenant

If you haven’t yet, you will need to create a new Azure AD B2C tenant. Follow this tutorial for instructions.

Create a user flow

image

For demo purposes we used the “Sign in” user flow but you can select whichever one makes sense for you. Any claims selected under “Application Claims” will be encoded into the ID token which is accessible during the /createuser and /syncuser endpoint.

image

Create an App Registration

Under “App Registrations” click on “New Registration”

image

Make sure you set Redirect URI to OrderCloud’s /ocrpcode endpoint. Please note that the base URL for all OrderCloud endpoints vary by environment/region so make sure to check your marketplace for the correct value.

image

Under “Overview” of your newly created app registration copy the “Application (client) ID”, this will be your ConnectClientID in future steps.

While still under “Overview”, click on “Endpoints”

image

There are two values specifically we are interested in:

  • Azure AD B2C OAuth 2.0 token endpoint (v2) - This will be your TokenEndpoint
  • Azure AD B2C OAuth 2.0 authorization endpoint (v2) - This will be your AuthorizationEndpoint

Next, under “Certificates & secrets” create a new client secret. Copy the generated value, this will be your ConnectClientSecret in future steps.

image

Start ngrok

You’ll need a publicly available endpoint. You can use a tool called ngrok to let us do this locally without having to deploy anything. After installing ngrok run the command ngrok http 3000. This tells ngrok to expose our endpoint (not yet running) on http://localhost:3000 to two public endpoints. After running the command copy either one of those URLs and record it, you’ll need it for the next step.

Create the OpenID Connect Integration Event

1POST https://sandboxapi.ordercloud.io/v1/integrationEvents HTTP/1.1
2Authorization: Bearer INSERT_ACCESS_TOKEN_HERE
3Content-Type: application/json; charset=UTF-8;
4
5{
6 "ID": "openidconnect",
7 "Name": "openidconnect",
8 "EventType": "OpenIDConnect",
9 "CustomImplementationUrl": "{your-ngrok-url}/integration-events",
10 "HashKey": "supersecrethash",
11 "ElevatedRoles": ["BuyerUserAdmin"]
12}
1import { Tokens, IntegrationEvents } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5IntegrationEvents.Create({
6 ID: "openidconnect",
7 Name: "openidconnect",
8 EventType: "OpenIDConnect",
9 CustomImplementationUrl: "{your-ngrok-url}/integration-events",
10 HashKey: "supersecrethash",
11 ElevatedRoles: ["BuyerUserAdmin"],
12})
13.then(response => {
14 // returns the newly created integration event
15 console.log(response);
16})
17.catch(err => console.log(err));
18
19})
1import { Tokens, IntegrationEvents, IntegrationEvent, OrderCloudError } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5const integrationEvent: IntegrationEvent = await IntegrationEvents.Create({
6 ID: "openidconnect",
7 Name: "openidconnect",
8 EventType: "OpenIDConnect",
9 CustomImplementationUrl: "{your-ngrok-url}/integration-events",
10 HashKey: "supersecrethash",
11 ElevatedRoles: ["BuyerUserAdmin"],
12})
13.catch((err:OrderCloudError) => console.log(err));
14console.log(integrationEvent);
1using OrderCloud.SDK;
2
3var client = new OrderCloudClient(...);
4
5await client.AuthenticateAsync();
6
7IntegrationEvent response = await client.IntegrationEvents.CreateAsync(new IntegrationEvent {
8 ID = "openidconnect",
9 Name = "openidconnect",
10 EventType = IntegrationEventType.OpenIDConnect,
11 CustomImplementationUrl = "{your-ngrok-url}/integration-events",
12 HashKey = "supersecrethash",
13 ElevatedRoles = new ApiRole[] { ApiRole.BuyerUserAdmin },
14});
OrderCloud Property
Description
ID
Unique identifier for the integration event
Name
A short name describing the integration event, this is not user facing
EventType
Indicates what type of integration event this is, in our case we should use OpenIDConnect
CustomImplementationUrl
This indicates the base URL of your middleware where OrderCloud should post to. For OpenIDConnect it will call out to the path /createuser and /syncuser
HashKey
This is an important security feature that is used by your middleware to validate that requests made to your endpoints are legitimate and come from OrderCloud
ElevatedRoles
An optional array of roles that will be encoded in the user’s token and sent along in the payload to /createuser and /syncuser. In our case we are defining BuyerUserAdmin so that our middleware endpoints have the roles necessary to create users on the fly.

Create a new OpenID Connect

This entity configures the connection between Azure and OrderCloud.

1POST https://sandboxapi.ordercloud.io/v1/openidconnects HTTP/1.1
2Authorization: Bearer INSERT_ACCESS_TOKEN_HERE
3Content-Type: application/json; charset=UTF-8;
4
5{
6 "ID": "azure",
7 "OrderCloudApiClientID": "CLIENT_ID_FROM_CREATING_API_CLIENT_STEP",
8 "ConnectClientID": "CLIENT_ID_FROM_CONFIGURING_AZURE",
9 "ConnectClientSecret": "CLIENT_SECRET_FROM_CONFIGURING_AZURE",
10 "AppStartUrl": "http://localhost:3000?token={0}&idpToken={1}&refreshToken={3}",
11 "AuthorizationEndpoint": "AUTH_ENDPOINT_FROM_AZURE",
12 "TokenEndpoint": "TOKEN_ENDPOINT_FROM_AZURE",
13 "UrlEncoded": false,
14 "CallSyncUserIntegrationEvent": true,
15 "IntegrationEventID": "openidconnect",
16 "AdditionalIdpScopes": ["CLIENT_ID_FROM_CONFIGURING_AZURE"] // If included, you will receive the azure token in addition to ordercloud token at the end of the flow
17}
1import { Tokens, OpenIdConnects } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5OpenIdConnects.Create({
6 ID: "azure",
7 OrderCloudApiClientID: "CLIENT_ID_FROM_CREATING_API_CLIENT_STEP",
8 ConnectClientID: "CLIENT_ID_FROM_CONFIGURING_AZURE",
9 ConnectClientSecret: "CLIENT_SECRET_FROM_CONFIGURING_AZURE",
10 AppStartUrl: "http://localhost:3000?token={0}&idpToken={1}&refreshToken={3}",
11 AuthorizationEndpoint: "AUTH_ENDPOINT_FROM_AZURE",
12 TokenEndpoint: "TOKEN_ENDPOINT_FROM_AZURE",
13 UrlEncoded: false,
14 CallSyncUserIntegrationEvent: true,
15 IntegrationEventID: "openidconnect",
16 AdditionalIdpScopes: ["CLIENT_ID_FROM_CONFIGURING_AZURE"] // If included, you will receive the azure token in addition to ordercloud token at the end of the flow
17})
18.then(response => {
19 // returns the newly created openidconnect
20 console.log(response);
21})
22.catch(err => console.log(err));
23
24})
1import { Tokens, OpenIdConnects, OpenIdConnect, OrderCloudError } from "ordercloud-javascript-sdk";
2
3Tokens.Set("INSERT_ACCESS_TOKEN_HERE")
4
5const openIdConnect = await OpenIdConnects.Create({
6 ID: "azure",
7 OrderCloudApiClientID: "CLIENT_ID_FROM_CREATING_API_CLIENT_STEP",
8 ConnectClientID: "CLIENT_ID_FROM_CONFIGURING_AZURE",
9 ConnectClientSecret: "CLIENT_SECRET_FROM_CONFIGURING_AZURE",
10 AppStartUrl: "http://localhost:3000?token={0}&idpToken={1}&refreshToken={3}",
11 AuthorizationEndpoint: "AUTH_ENDPOINT_FROM_AZURE",
12 TokenEndpoint: "TOKEN_ENDPOINT_FROM_AZURE",
13 UrlEncoded: false,
14 CallSyncUserIntegrationEvent: true,
15 IntegrationEventID: "openidconnect",
16 AdditionalIdpScopes: ["CLIENT_ID_FROM_CONFIGURING_AZURE"] // If included, you will receive the azure token in addition to ordercloud token at the end of the flow
17})
18.catch((err:OrderCloudError) => console.log(err));
19console.log(openIdConnect);
1using OrderCloud.SDK;
2
3var client = new OrderCloudClient(...);
4
5await client.AuthenticateAsync();
6
7OpenIdConnect response = await client.OpenIdConnects.CreateAsync(new OpenIdConnect {
8 ID = "azure",
9 OrderCloudApiClientID = "CLIENT_ID_FROM_CREATING_API_CLIENT_STEP",
10 ConnectClientID = "CLIENT_ID_FROM_CONFIGURING_AZURE",
11 ConnectClientSecret = "CLIENT_SECRET_FROM_CONFIGURING_AZURE",
12 AppStartUrl = "http://localhost:3000?token={0}&idpToken={1}&refreshToken={3}",
13 AuthorizationEndpoint = "AUTH_ENDPOINT_FROM_AZURE",
14 TokenEndpoint = "TOKEN_ENDPOINT_FROM_AZURE",
15 UrlEncoded = false,
16 CallSyncUserIntegrationEvent = true,
17 IntegrationEventID = "openidconnect",
18 AdditionalIdpScopes = ["CLIENT_ID_FROM_CONFIGURING_AZURE"] // If included, you will receive the azure token in addition to ordercloud token at the end of the flow
19});
OrderCloud Property
Description
ID
Unique identifier for the connect client config
OrderCloudApiClientID
This is the clientID (on OrderCloud) that wants to authenticate via OpenID Connect
ConnectClientID
This is the clientID of the identify provider (in this case Azure AD B2C)
ConnectClientSecret
This is the clientSecret of the identity provider (in this case Azure AD B2C)
AppStartUrl
This is where the user will be redirected to after successful authentication via OpenID Connect. The parameter {0} represents the OrderCloud token. The parameter {1} represents the IDP token, and the parameter {2} which is not used here represents the app start path used for deep linking
AuthorizationEndpoint
Defined by the OpenID provider (in this case Azure AD B2C). It is the endpoint OrderCloud will redirect the user to in order to validate credentials.
TokenEndpoint
Defined by the OpenID provider (in this case Azure AD B2C). It is the endpoint OrderCloud will call out to get a token from the provider.
UrlEncoded
How to post information to the OpenID provider (in this case Azure AD B2C). It is sent with either Basic Auth if UrlEncoded is false, otherwise it posts a Url encoded body. Most providers (such as Azure) will accept both. If you encounter an error with your provider a good first step for troubleshooting is changing this value to the opposite of what is set.
CallSyncUserIntegrationEvent
Whether or not the /syncuser endpoint will be called which is used to update user details that may have changed after their initial login
IntegrationEventID
The ID to the Integration Event created in previous step. This has information about which endpoint OrderCloud should call out to in order to create the user after the user has successfully logged in.
AdditionalIdpScopes
As defined by the OIDC specification we will request profile, email, and oidc scope but you may request any additional scopes you’d like to request from the IDP at the time of authentication.

Testing

OrderCloud and Azure should now be completely configured, and you are ready to test to make sure everything is working. To simplify this aspect, we’ve created a very minimal frontend to test this functionality.

  1. Clone this repository
  2. Install dependencies by running npm install at the root of the project
  3. Copy .env.example to .env.local
  4. Run the project by running npm run start. This will start the server on port 3000. Remember ngrok is already listening to this port and will expose our endpoints publicly.
  5. Navigate to the url http://localhost:3000. If everything is correct you should be redirected to Azure’s login page. Upon signing in you will be redirected back to the application and should see details about your logged in user

Be sure to look at the /createuser and /syncuser endpoints

Common Issues

Error message: “error validating token with authority”

This issue occurs when OrderCloud attempts to retrieve the ID token from the IDP. This is generally a configuration issue. Confirm ConnectClientID, ConnectClientSecret, and OrderCloudClientID are correct.

Conclusion

By now you should have a solid understanding of how to implement single sign-on with Azure and you should now feel empowered to build the same with any number of other identity providers.


Still have questions?
Ask in our Community Channel

Sitecore Logo

© Copyright 2026, Sitecore OrderCloud®. All rights reserved.

Contact Us
Privacy Policy
Sitecore