How do I create a user in code, add it Azure AD B2C, then log in as that user using Azure AD B2C OAuth (MSAL) - azure-ad-b2c

Having read the documentation here I thought I should be able to add a user to active directory B2C and then be able to log in as that user. The error message is: "We can't seem to find your account"
[TestMethod]
public async Task CreateUserTest()
{
string mailNickname = Guid.NewGuid().ToString();
string upn = mailNickname + "#mydomain.onmicrosoft.com";
string email = "zzz#gmail.com";
User record = new User { Email = email, DisplayName = "Bob Smith", MailNickname = mailNickname, UserPrincipalName = upn };
record.Identities = new List<ObjectIdentity>();
record.PasswordProfile = new PasswordProfile();
record.Identities.Append(new ObjectIdentity { Issuer = "mydomain.onmicrosoft.com", IssuerAssignedId = email, ODataType = "microsoft.graph.objectidentity", SignInType = "emailAddress" });
record.Identities.Append(new ObjectIdentity { Issuer = "mydomain.onmicrosoft.com", IssuerAssignedId = upn, ODataType = "microsoft.graph.objectidentity", SignInType = "userPrincipalName" });
record.PasswordProfile.Password = "Abcdefgh123!!";
record.AccountEnabled = true;
record.PasswordProfile.ForceChangePasswordNextSignIn = false;
User user = await graphService.CreateUser(record);
Assert.IsNotNull(user);
}
public async Task<User> CreateUser(User user)
{
var result = await client.Users.Request().AddAsync(user);
return user;
}
This login code works if the user logs in using an existing account or creates a new one using the Sign up now link:
export const SignIn = async (appState: AppState): Promise<string> => {
var msg: string = '';
try {
const response = await MSAL.login('loginPopup');
Edit: Add screen cap showing user type and source:

I tried to create a consumer user with code like yours:
And tested with this account in user flow, it returned the token well:
Please check the accounts that you created in your code, the User type always need to be Member and have the Source Azure Active Directory.

Related

Get AccountName / UPN in a UWP App when logged on in Azure

I am creating a UWP app which shows certain data, depending on the logged on user.
The user is logged on in Windows Azure and the computer account is also joined to Azure.
I have enabled the "Account Information" feature in the app manifest.
I am trying to find out the user data, using the User Class, like mentioned in several examples online:
private async void GetAllUserData()
{
var users = await User.FindAllAsync();
foreach (var user in users)
{
var authenticationStatus = user.AuthenticationStatus;
var nonRoamableId = user.NonRoamableId;
var provider = await user.GetPropertyAsync(KnownUserProperties.ProviderName);
var accountName = await user.GetPropertyAsync(KnownUserProperties.AccountName);
var displayName = await user.GetPropertyAsync(KnownUserProperties.DisplayName);
var domainName = await user.GetPropertyAsync(KnownUserProperties.DomainName);
var principalName = await user.GetPropertyAsync(KnownUserProperties.PrincipalName);
var firstName = await user.GetPropertyAsync(KnownUserProperties.FirstName);
var guestHost = await user.GetPropertyAsync(KnownUserProperties.GuestHost);
var lastName = await user.GetPropertyAsync(KnownUserProperties.LastName);
var sessionInitiationProtocolUri = await user.GetPropertyAsync(KnownUserProperties.SessionInitiationProtocolUri);
var userType = user.Type;
}
}
The only properties I can get from the user object are:
DisplayName
AuthenticationStatus
NonRoamableId
UserType
All other properties remain empty. From my understanding, when I am logged in to Windows Azure, at least the principal name should have a value.
What am I doing wrong - or in other words - what do I have to do, to get account information?
After enabling "Enterprise Authentication" feature in my app manifest, the UPN is filled in the principalName variable.
I know, this does not the real authentication job for the application, but for my purpose it is sufficient to have the UPN, authenticated in Windows.
For more information about adding Azure authentication to an app I have found the following links:
https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-windows-store-dotnet-get-started-users
https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-native-uwp-v2/

Get Azure Roles in the Web App

I created a WebApp in Azure. I have the authentication based on the AzureAD...
Actually all users have the same rights... I need to have a group of Administrators and the rest of the world.
I see that in the Azure Portal for my web app there is a Acces control (IAM) where some roles are listed...
Can I use these roles in my Application?
What actually I do in my View is:
var isAdmin = User.HasClaim("IsAdmin", true.ToString());
If I understand correctly that is named "Claims Based" authentication, but I would like to try to use the Role Based Authentication...
I tried also to do
var userIdentity = (System.Security.Claims.ClaimsIdentity)User.Identity;
var claims = userIdentity.Claims;
var roleClaimType = userIdentity.RoleClaimType;
var roles = claims.Where(c => c.Type == System.Security.Claims.ClaimTypes.Role).ToList();
but that roles list is empty...
Here is the my Startup.cs Autentication code in the public void Configure(IApplicationBuilder app,...
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["Authentication:AzureAd:ClientId"],
Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"],
CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"],
Events = new OpenIdConnectEvents
{
OnTicketReceived = async context =>
{
var user = (ClaimsIdentity)context.Ticket.Principal.Identity;
if (user.IsAuthenticated)
{
var firstName = user.FindFirst(ClaimTypes.GivenName).Value;
var lastName = user.FindFirst(ClaimTypes.Surname).Value;
var email = user.HasClaim(cl => cl.Type == ClaimTypes.Email) ? user.FindFirst(ClaimTypes.Email).Value : user.Name;
var connectedOn = DateTime.UtcNow;
var userId = user.Name;
var myUser = await repository.GetAsync<Connection>(userId);
if (myUser == null)
{
myUser = new Connection(userId)
{
FirstName = firstName,
LastName = lastName,
Email = email
};
}
myUser.LastConnectedOn = connectedOn;
List<Connection> myList = new List<Connection>() { myUser };
var results = await repository.InsertOrMergeAsync(myList);
Claim clm = new Claim("IsAdmin", myUser.IsAdmin.ToString(), ClaimValueTypes.Boolean);
user.AddClaim(clm);
}
return;
}
},
}
});
And also my appsettings.json
"Authentication": {
"AzureAd": {
"AADInstance": "https://login.microsoftonline.com/",
"CallbackPath": "/signin-oidc",
"ClientId": "xxxxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxxxx",
"Domain": "mysite.azurewebsites.net",
"TenantId": "xxxxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxxxx"
}
}
I believe that the roles that you observed in the portal are related to the administration of the Web Apps, not the authorization to features it exposes.
To use roles programmatically, I suggest that you look at the following sample which explains how to setup the roles in the Azure AD application corresponding to the project that you deployed as a Web App.
https://github.com/Azure-Samples/active-directory-dotnet-webapp-roleclaims
This way you'll be able to protect pages (from the code in the controller) using attributes:
``
[Authorize(Roles = "Admin, Observer, Writer, Approver")]
See https://github.com/Azure-Samples/active-directory-dotnet-webapp-roleclaims/blob/master/WebApp-RoleClaims-DotNet/Controllers/TasksController.cs#L17
You can also test for users having given roles:
if (User.IsInRole("Admin") || User.IsInRole("Writer"))
{
...
}

Azure AD Add AppRoleAssignment

I am using Azure AD for the authentication service on an MVC application. I am managing the user accounts successfully using the Graph API. I am trying to add an AppRoleAssignment to the user.
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
Uri servicePointUri = new Uri(graphResourceID);
Uri serviceRoot = new Uri(servicePointUri, tenantID);
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await GetTokenForApplication());
IUser user = new User();
user.JobTitle = "Tester";
user.DisplayName = "Test Tester";
user.Surname = "Tester";
user.GivenName = "Test";
user.UserPrincipalName = "ttester#test.com";
user.AccountEnabled = true;
user.MailNickname = "ttester";
user.PasswordProfile = new PasswordProfile
{
Password = "XXXXX",
ForceChangePasswordNextLogin = true
};
await activeDirectoryClient.Users.AddUserAsync(user);
var appRoleAssignment = new AppRoleAssignment
{
Id = Guid.Parse("XXXXX"),
ResourceId = Guid.Parse("XXXXX"),
PrincipalType = "User",
PrincipalId = Guid.Parse(user.ObjectId)
};
user.AppRoleAssignments.Add(appRoleAssignment);
await user.UpdateAsync();
The AppRoleAssignment is never made. I am not certain if it is the constructor variables.
The id I am placing the ID of the role, being created in the application manifest. The ResourceId I am placing the ObjectId of the application. The application is created under the AAD Directory.
The code actually completes without error, however inspecting the user it shows not AppRoleAssignments.
In the end I am trying to implement RBAC using application roles.
Any help is greatly appreciated.
To assign application role to a user, you need to cast the User object to IUserFetcher:
await ((IUserFetcher)user)
.AppRoleAssignments.AddAppRoleAssignmentAsync(appRoleAssignment);
I also had to set the ResourceId to the ServicePrincipal.ObjectId
var servicePrincipal = (await
activeDirectoryClient.ServicePrincipals.Where(
s => s.DisplayName == "MyApplicationName").ExecuteAsync()).CurrentPage
.First();
var appRoleAssignment = new AppRoleAssignment
{
Id = Guid.Parse("XXXXX"),
// Service principal id go here
ResourceId = Guid.Parse(servicePrincipal.ObjectId),
PrincipalType = "User",
PrincipalId = Guid.Parse(user.ObjectId)
};

Sign up event from Azure B2C

How would I know that a person on my website has just completed the 'Sign Up' process in Azure B2C? Would I have to store my own list of object ids and check against it? I get the feeling I am going to have to do that in any case...
You receive the 'newUser' boolean claim if you have this selected in the signup policy. This will only be sent once, so you need to act on this.
Yes you need to store a list of object id's & verify accordingly in your business logic.
Managed to solve this...
Step 1 (Azure Portal)
In Azure Portal navigate to ADB2C, click on your Sign up and sign in policy
Click on Application claims and make sure to Tick the "User is new" claim
The "User is new" claim will be sent only if the user has just signed-up
Step 2 (Code)
//Using OpenIdConnectOptions
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = OnRedirectToIdentityProvider,
OnRemoteFailure = OnRemoteFailure,
OnAuthorizationCodeReceived = OnAuthorizationCodeReceived,
OnAuthenticationFailed = OnAuthenticationFailed,
OnMessageReceived = OnMessageReceived,
OnRedirectToIdentityProviderForSignOut = OnRedirectToIdentityProviderForSignOut,
OnRemoteSignOut = OnRemoteSignOut,
OnSignedOutCallbackRedirect = OnSignedOutCallbackRedirect,
OnTicketReceived = _onTicketReceivedInternal,
OnTokenResponseReceived = OnTokenResponseReceived,
OnTokenValidated = OnTokenValidated,
OnUserInformationReceived = OnUserInformationReceived
};
Notice the _onTicketReceivedInternal Task...
private Task _onTicketReceivedInternal(TicketReceivedContext context)
{
this.OnTicketReceived(context);
//Check if new user
Claim newUserClaim = context.Principal.Claims.ToList().FirstOrDefault(x => x.Type == "newUser");
bool newUser = newUserClaim == null ? false : true;
//Trigger event
if (newUser)
this.OnSignUp(context);
return Task.FromResult(0);
}
//Custom method OnSignUp where an application can do something on user sign up
protected virtual Task OnSignUp(TicketReceivedContext context)
{
return Task.FromResult(0);
}

Getting the email from external providers Google and Facebook during account association step in a default MVC5 app

Apparently you can do this with the Facebook provider by adding scopes to the FacebookAuthenticationOptions object in Startup.Auth.cs:
http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx
List<string> scope = new List<string>() { "email" };
var x = new FacebookAuthenticationOptions();
x.Scope.Add("email");
...
app.UseFacebookAuthentication(x);
How to do the same with Google provider? There isn't a x.Scope property for the GoogleAuthenticationOptions class/object!
PLEASE SEE UPDATES AT THE BOTTOM OF THIS POST!
The following works for me for Facebook:
StartupAuth.cs:
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = "x",
AppSecret = "y"
};
facebookAuthenticationOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
ExternalLoginCallback method:
var externalIdentity = HttpContext.GetOwinContext().Authentication.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
And for Google:
StartupAuth.cs
app.UseGoogleAuthentication();
ExternalLoginCallback method (same as for facebook):
var externalIdentity = HttpContext.GetOwinContext().Authentication.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
If I set a breakpoint here:
var email = emailClaim.Value;
I see the email address for both Facebook and Google in the debugger.
Update 1: The old answer had me confused so I updated it with the code I have in my own project that I just debugged and I know works.
Update 2: With the new ASP.NET Identity 2.0 RTM version you no longer need any of the code in this post. The proper way to get the email is by simply doing the following:
Startup.Auth.cs
app.UseFacebookAuthentication(
appId: "x",
appSecret: "y");
app.UseGoogleAuthentication();
AccountController.cs
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInHelper.ExternalSignIn(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresTwoFactorAuthentication:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
You need to explicitly configure the FacebookAuthenticationOptions to get the email address from the authenticated user.
In your MVC5 project, add these lines in the Startup.Auth.cs
var options = new FacebookAuthenticationOptions() {
AppId = "xxxxxxxx",
AppSecret = "xxxxxxxxx"
};
options.Scope.Add("email");
app.UseFacebookAuthentication(options);
Update
Reduced my sample code to the absolute minimum. Your updated code works fine by the way, I have also tried it with both Facebook and Google.
In ASP.NET Core Facebook authentication the Facebook middleware seems to no longer pass in the email, even if you add it to the scope. You can work around it by using Facebook's Graph Api to request the email.
You can use any Facebook Graph Api client or roll your own, and use it to invoke the Graph api as follows:
app.UseFacebookAuthentication(options =>
{
options.AppId = Configuration["Authentication:Facebook:AppId"];
options.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
options.Scope.Add("public_profile");
options.Scope.Add("email");
options.Events = new OAuthEvents
{
OnCreatingTicket = context => {
// Use the Facebook Graph Api to get the user's email address
// and add it to the email claim
var client = new FacebookClient(context.AccessToken);
dynamic info = client.Get("me", new { fields = "name,id,email" });
context.Identity.AddClaim(new Claim(ClaimTypes.Email, info.email));
return Task.FromResult(0);
}
};
});
You can find a more detailed example about how to use it here: http://zainrizvi.io/2016/03/24/create-site-with-facebook-login-using-asp.net-core/#getting-the-email-address-from-facebook

Resources