
The Microsoft Graph and Entra documentation are both very comprehensive and provide a lot of information - but they often contain no examples or no working examples. In addition, the SDKs are either not, insufficiently or incorrectly documented.
In this article, I will show you how to create a local user in Entra External Identities with Microsoft Graph - and this example even works!
Requirements
- You are using the Microsoft.Graph
NuGet package, currently version
5.69.0.
Necessary information
- You need your tenant domain (either
mydomain.onmicrosoft.comor a custom domain likemydomain.com)
Basically, using an Entra Local account means that you are primarily responsible for the administration. And although the documentation tells you that certain values are set automatically, you have to do this yourself. The SDK is simply incorrectly documented here - as is unfortunately so often the case.
Creation of the user principal name
The User Principal Name (UPN) is the unique identification of a user in your tenant. It basically consists of the user name and the domain. With an external user, however, you do not have a user name and have to think of something yourself. The domain is mandatory and must correspond to the domain of your tenant (e.g. mydomain.onmicrosoft.com). Something the SDK could have done for you, but good SDKs are unfortunately in short supply. For the user name, I make it easy for myself and use a prefix together with the UserId that the user has(will have in the database in my system.
The format therefore corresponds to user_<userid>@<domain>
At the end it looks like user_25e54711-a403-4290-a47e-d228ff7beb28@mydomain.onmicrosoft.com
This allows me to link the entry in Entra with the entry in the database at any time (for example to cleanup corrupt entries). The user itself is later linked to the Entra Object Id in the database, as recommended by Microsoft.
Creating a user
The actual creation of the user is simple if you know what you need. Even if the SDK tells you that property XYZ has to be set, but property X does not - absolute nonsense. You are bombarded with useless error messages.
What works is the following minimal setup:
1// inputs
2Guid userId = ...
3string emailAddress = ...
4string password = ...
5
6// create new user object
7string newUpn = $"user_{userId}@{options.TenantDomain}";
8
9User newUser = new()
10{
11 AccountEnabled = false, // enable this, if you want
12 DisplayName = emailAddress,
13
14 PasswordPolicies = "DisablePasswordExpiration,DisableStrongPassword", // remove this, if you want
15 PasswordProfile = new PasswordProfile
16 {
17 ForceChangePasswordNextSignIn = false,
18 Password = password
19 },
20
21 // Local User - all properties are required
22 CreationType = "LocalAccount",
23 UserType = "Member",
24 UserPrincipalName = newUpn,
25 Identities =
26 [
27 new() {
28 SignInType = "emailAddress",
29 Issuer = _clientContext.Domain,
30 IssuerAssignedId = emailAddress
31 }
32 ]
33};
This setup…
- … creates a local user with a password
- … the password policies for expiration and password rules are disabled (we don’t want expiration and we have slightly customized rules for Entra - but you can just leave both enabled if you want)
- … the account is initially deactivated (as the user must first validate their e-mail address, after which the account is activated)
- … the user name is generated from the e-mail address
- … the user is created as
LocalAccount, which is a fixed value - as is the UserTypeMember, - … the login takes place via the e-mail address, which we pass as
emailAddress. - … the
Issueris the domain of your tenant
Actually, creating a local user in Entra External Identities is not that difficult - if you know which values you have to set with which content. Maybe one day we will get a great Graph SDK and a useful documentation :-)
Related articles

Dec 05, 2025 · 5 min read
IMemoryCache Entry Invalidation (Manual Cache Busting)
IMemoryCache is great for speeding up expensive operations (database reads, HTTP calls, heavy computations). But many real systems need more …

Nov 08, 2025 · 8 min read
.NET 10 Release: What's New (LTS) and What to Upgrade First
.NET 10 is the next Long-Term Support (LTS) release in the .NET family. LTS matters because it’s the version many teams standardize on …

Sep 24, 2025 · 9 min read
Automatically discover tools for Azure OpenAI Realtime API
Azure now provides a unified Realtime API for low‑latency, multimodal conversations over WebRTC or WebSockets. If you’ve used the earlier …
Let's Work Together
Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.

Comments