Forum Discussion

MustangProgrammer's avatar
MustangProgrammer
Copper Contributor
Oct 07, 2025

Creating SSO Application using Microsoft Graph

I'm attempting to create SSO applications using Microsoft Graph to migrate from ADFS.  

"Microsoft.Graph.Models.Application requestBody = new Microsoft.Graph.Models.Application
                {
                    IdentifierUris = new List<string>() { appURL },
                    DisplayName = appName,
                    Web = new Microsoft.Graph.Models.WebApplication
                    {
                        RedirectUris = new List<string>
                        {
                            appURL
                        },

                    }

                };
                retVal = await graphClient.Applications.PostAsync(requestBody);"

My URI requires a trailing slash.  When I try to use the trailing slash I get the error: "Application alias 'https://xxxx/aspx/xxxx/' value is invalid."

I tried editing my realm to remove the trailing slash, and the redirect URI, e.g. https://xxxx/aspx/xxxx, but removing them causes sign-in issues.  If I edit the the Identifier URI in the Entra ID portal, to add the trailing slash, I am able to sign in and use the application.  Though it is a solution, I need to use the application to enter close to 240 applications total between all of our environments.  Anyone run into this and have ideas I can try?

 

Thanks.

4 Replies

  •  

    For modern development, you should use Microsoft.Graph v5. The code you have is written using the v5 SDK syntax.

    Required NuGet Package:

    • Microsoft.Graph - Version 5.0.0 or newer.

    This single package will bring in all the necessary dependencies, including the models (Microsoft.Graph.Models) and the core client libraries needed to make the API calls.

    Detailed Breakdown and Why Version Matters

    The Microsoft Graph .NET SDK underwent a significant and breaking change between version 4.x and version 5.x. Your code is written using the newer, more streamlined syntax of v5.

    Let's look at the specific lines in your code that confirm this:

    1. await graphClient.Applications.PostAsync(requestBody);
      • In v5, the method to create a resource is a direct PostAsync call on the collection (.Applications). This is exactly what your code does.
      • In the older v4, the syntax was more verbose: await graphClient.Applications.Request().AddAsync(requestBody);. Your code would not compile with the v4 SDK.
    2. await graphClient.Applications[createdApplication.Id].PatchAsync(updateRequestBody);
      • In v5, accessing a specific resource by its ID is done using an indexer ([id]), followed by the PatchAsync method. This matches your code perfectly.
      • In the older v4, this would have been await graphClient.Applications[createdApplication.Id].Request().UpdateAsync(updateRequestBody);.
    3. using Microsoft.Graph.Models;
      • In v5, all the data models (like Application, WebApplication, etc.) were moved into a separate Microsoft.Graph.Models namespace, which is referenced at the top of your file.
      • In v4, these models were in the base Microsoft.Graph namespace.

    How to Add the Correct Package in Visual Studio

    You can add the correct package using the NuGet Package Manager:

    1. Via Package Manager Console:
    2. (It's good practice to specify a recent version rather than just the minimum)
    3. powershell
    4. Install-Package Microsoft.Graph -Version 5.49.0
    5. Via NuGet Package Manager UI:
      • Right-click on your project in the Solution Explorer and select "Manage NuGet Packages...".
      • Go to the "Browse" tab.
      • Search for Microsoft.Graph.
      • Select the package and ensure the version is 5.0.0 or higher.
      • Click "Install".

    What About Authentication?

    The code snippet you provided assumes you have already created and authenticated a GraphServiceClient instance named graphClient. To do that, you will also need an authentication library.

    The recommended library to pair with the v5 SDK is:

    • Azure.Identity

    You would use it to create a credential object (like ClientSecretCredential or DeviceCodeCredential) which is then passed to the GraphServiceClient.

    Example of creating the graphClient:

    C#

    using Azure.Identity;
    using Microsoft.Graph;

    // Tenant ID, Client ID, and Client Secret for your app registration
    var tenantId = "YOUR_TENANT_ID";
    var clientId = "YOUR_CLIENT_ID";
    var clientSecret = "YOUR_CLIENT_SECRET";

    // The scopes your app needs. For managing applications, you need 'Application.ReadWrite.All'
    var scopes = new[] { "https://graph.microsoft.com/.default" };

    // Create the credential object
    var clientSecretCredential = new ClientSecretCredential(
        tenantId, clientId, clientSecret );

    // Create the GraphServiceClient
    var graphClient = new GraphServiceClient(clientSecretCredential, scopes);

    // ... now your provided code will work ...

    Summary:

    To make your code work, you need to be using version 5.x of the Microsoft.Graph NuGet package. This version directly matches the syntax and structure of your two-step "create, then update" logic.

     

  • Ankit365's avatar
    Ankit365
    Iron Contributor

    this is a known quirk with the Microsoft Graph Applications endpoint that has been reported by multiple admins migrating from AD FS to Entra ID. As of October 2025, the Graph API validation rules for the IdentifierUris property still reject values that contain a trailing slash, even though the Azure Portal allows it and it works perfectly in practice.

    Here’s what’s happening. When you submit an app registration through the Graph API, the backend enforces stricter normalization rules for the identifierUris field than the portal does. Trailing slashes are automatically trimmed or flagged as invalid because the system treats the URI as an identifier rather than a navigable endpoint. However, the portal UI uses a different validation layer that allows a trailing slash and later normalizes it internally. That is why creating the app manually in the portal works while the API call fails.

    To handle this when bulk-creating hundreds of apps:

    Programmatically omit the trailing slash in your Graph creation call for IdentifierUris, since it’s treated as a unique identifier. Example: use https://xxxx/aspx/xxxx instead of https://xxxx/aspx/xxxx/.

    Include the trailing slash only in RedirectUris under the web object, because redirect URIs are allowed to have it. The sign-in redirect validation uses the web.redirectUris property, not the identifierUris value, during token issuance.

    Once created, if your environment absolutely requires the trailing slash in the Identifier URI (for legacy compatibility), you can perform a PATCH operation via the portal or the Graph beta endpoint after creation, which relaxes that validation.
    see below workaround, if it fixes your issue.

    var app = new Application
    {
        DisplayName = appName,
        IdentifierUris = new List<string> { appURL.TrimEnd('/') },
        Web = new WebApplication
        {
            RedirectUris = new List<string> { appURL } // keep the slash here
        }
    };
    await graphClient.Applications.PostAsync(app);

    This allows you to automate creation of all 240 apps without triggering the invalid URI error. Afterward, if the trailing slash in the Identifier URI is still required for a few specific apps, you can patch those manually in the portal or via PowerShell (Set-AzureADApplication) where the enforcement is looser. Use the slash-free URI for IdentifierUris and keep the slash in RedirectUris to maintain compatibility until Microsoft aligns validation across Graph and the portal. Please hit like if you like the solution.

  • The Root Cause: identifierUris vs. replyUrls

    The core of the issue lies in the different validation rules for two distinct properties:

    1. 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.
    2. 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.

    1. 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 ).
    2. 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.

    • MustangProgrammer's avatar
      MustangProgrammer
      Copper Contributor

      Can you tell which version of Microsoft graph you are using?  I'm using 5.93.0.0, which does not allow me to do this pattern.  I still get the same error, on the patch.

Resources