Forum Discussion
Creating SSO Application using Microsoft Graph
The Root Cause: identifierUris vs. replyUrls
The core of the issue lies in the different validation rules for two distinct properties:
- identifierUris (The Application's "Name"):
- This is the unique name that identifies your application across all of Microsoft Entra ID. It's the "Audience" (or aud claim) in a SAML or JWT token.
- The Graph API enforces a strict format for this field, which disallows trailing slashes. It sees https://app.contoso.com/ as an invalid "alias" or path-based identifier. It expects something that looks like a domain or a scheme, like https://app.contoso.com or api://my-app-guid.
- web.redirectUris (The "Reply URL" ):
- This is the list of endpoints where Entra ID is allowed to send the security token after a user successfully authenticates.
- This field is much more flexible and allows trailing slashes, as it needs to match the exact URL the application sends in its authentication request. Your application is likely sending https://xxxx/aspx/xxxx/ as its reply URL, which is why removing the slash here breaks the sign-in flow.
The Entra ID portal is more forgiving and often "corrects" or allows the trailing slash in the Identifier URI for backward compatibility, but the API does not offer this flexibility.
The Solution: The Two-Step "Create, Then Update" Pattern
Since you cannot create the application with the desired identifierUris format in a single step, you must do it in two separate API calls. This is the standard and recommended pattern for this scenario.
- Step 1: Create the Application with a Valid Temporary Identifier URI.
- Create the application object, but use a placeholder or a version of the URI without the trailing slash for the identifierUris.
- Crucially, keep the redirectUris exactly as your application needs it (with the trailing slash ).
- Step 2: Immediately Update the Application to Add the Correct Identifier URI.
- After the application is successfully created, you will have its id (the Object ID).
- Use this id to make a PATCH request to update the application. The update operation is more lenient and will often accept the trailing slash that the POST (create) operation rejected.
C# Code Example (Using Your Existing Code)
Here is how you would modify your C# code to implement this two-step pattern.
// Your existing variables
string appURL = "https://xxxx/aspx/xxxx/"; // The URL your app requires, with trailing slash
string appName = "My Awesome App";
// --- Step 1: Create the Application with a VALID Identifier URI ---
// Create a temporary, valid Identifier URI by trimming the trailing slash.
string tempIdentifierUri = appURL.TrimEnd('/' );
var initialRequestBody = new Microsoft.Graph.Models.Application
{
DisplayName = appName,
// Use the temporary, valid URI for creation.
IdentifierUris = new List<string> { tempIdentifierUri },
Web = new Microsoft.Graph.Models.WebApplication
{
// Use the CORRECT Redirect URI with the trailing slash.
RedirectUris = new List<string> { appURL }
}
};
// POST the request to create the application
Microsoft.Graph.Models.Application createdApplication = await graphClient.Applications.PostAsync(initialRequestBody);
Console.WriteLine($"Successfully created app '{appName}' with ID: {createdApplication.Id}");
// --- Step 2: Update the Application with the Required Identifier URI ---
// Now, create a new Application object for the PATCH request.
// This object only needs to contain the properties you want to change.
var updateRequestBody = new Microsoft.Graph.Models.Application
{
// Set the IdentifierUris to the final, desired value with the trailing slash.
IdentifierUris = new List<string> { appURL }
};
// Use the ID of the created application to send a PATCH request.
await graphClient.Applications[createdApplication.Id].PatchAsync(updateRequestBody);
Console.WriteLine($"Successfully updated Identifier URI for app '{appName}' to include trailing slash.");
// The 'retVal' is now the fully configured application object.
var retVal = await graphClient.Applications[createdApplication.Id].GetAsync();
Why This Works
- The initial POST request satisfies the API's strict validation rules for creating a new application object by providing a clean identifierUris.
- The subsequent PATCH request targets an existing object. The validation logic for updates is often different and more permissive, allowing you to set the identifierUris to the format that the Entra ID portal itself uses. This aligns the API-created object with what you can configure manually.
This approach is reliable, fully scriptable, and will allow you to automate the creation of all 240 of your applications with the exact configuration they need to function correctly, saving you an immense amount of manual effort and potential errors.