Using ASP.NET Core I am creating a system to invite users to Join a Group, Get Free Credits, ...
When inviting a User to Join a Group I create an Invitation which is saved in the database:
The token is saved on the database along with other information:
Invitation invitation = new Invitation {
InvitationType = "JoinGroup",
Completed = false,
Expiry = DateTime.Now.AddDays(4),
Token = some_token,
Parameters = new List<Parameter> {
new Parameter { Name = "GroupId", Value = 22 },
new Parameter { Name = "RoleId", Value = "Admin" },
new Parameter { Name = "Email", Value = "someuser#name.com" },
}
}
Then I send an email with an url:
/invite?token=some_token
When the user accesses the url I get the record with the given token.
With that information I do whatever I need to do, for example, add User to the Group.
Question
How should I create a unique token?
Which information should I include in the token?
And how should I validate it?
ASP.NET Core Identity provides functionality for generating tokens for different purposes.
Using the UserManager you can generate tokens for multiple purposes.
One of the methods available is the UserManager.GenerateUserTokenAsync(TUser, String, String).
You can verify the token using the UserManager.VerifyUserTokenAsync(TUser, String, String, String) method.
Reference To Documentation
Here is link that will help you getting started:
Identity Tokens
Related
Using the Microsoft Graph API in C# I can successfully get a user's details and update say their first name, or details held in extension attributes. However, is it possible to update the email address that they use to sign in with?
I can see this held in the Identities section, but I can't see a way of updating the values held there.
is it possible to update the email address that they use to sign in
with?
if you refer to User.identities property which:
Represents the identities that can be used to sign in to this user
account.
then yes it is supported to update this property.
Note: Updating the identities property requires the
User.ManageIdentities.All permission
PATCH https://graph.microsoft.com/v1.0/users/{id-or-upn}
{
"identities": [
{
"signInType": "emailAddress",
"issuer": "{tenant-name}",
"issuerAssignedId": "{user-signIn-email}"
}
]
}
C# example
var tenant = "contoso.onmicrosoft.com";
var existingEmailAddress = "current_email#contoso.com";
var newEmailAddress = "new_email#contoso.com";
//1 . find user
var users = await graphClient.Users
.Request()
.Filter($"identities/any(c:c/issuerAssignedId eq '{existingEmailAddress}' and c/issuer eq '{tenant}')")
.Select("displayName,id,userPrincipalName")
.GetAsync();
var foundUser = users.FirstOrDefault();
//2. update user identity
var user = new User
{
Identities = new List<ObjectIdentity>()
{
new ObjectIdentity
{
SignInType = "emailAddress",
Issuer = tenant,
IssuerAssignedId = newEmailAddress
}
}
};
await graphClient.Users[foundUser.Id].Request().UpdateAsync(user);
userPrincipalName is the field that you need to update. As per Update User Docs Using body below works for me.
PATCH https://graph.microsoft.com/v1.0/users/{USER-ID}
{
"userPrincipalName": "alias#domain.com"
}
Add this field to the C# call and should work.
This question already has answers here:
JWT Authentication - UserManager.GetUserAsync returns null
(3 answers)
Closed 2 years ago.
I am using asp.net mvc with work/school accounts authentication. Currently I'm trying to implement identity into to the user process.
Here is my ApplicationUser class:
public class ApplicationUser: IdentityUser
{
public ICollection<Semester> Semesters { get; set; }
}
So far, identity works just fine, there is just one problem. When I log into the app with my school account, I can call the ClaimsPrincipals as User in the Controllers. To get the current ApplicationUser you can use the UserManager (await _userManager.GetUserAsync(User), with User being the ClaimsPrincipals) But since I haven't stored my school account in the database, the result will be null. If I create a new ApplicationUser like the following
var newUser = new ApplicationUser()
{
UserName = User.Identity.Name,
Email = User.Identity.Name
};
await _userManager.CreateAsync(newUser);
await _userManager.AddClaimsAsync(newUser, User.Claims);
This will succesfully create and save the new user to the database with the claims. But then when I try to get the new created ApplicationUser with await _userManager.GetUserAsync(User) the result will still be null. If I access my DbContext and get all ApplicationUsers, the newly created ApplicationUser is there. So, how can I create an ApplicationUser based on the ClaimsPrincipals I get from my school account login?
Credits to #poke for this.
UserManager.GetUserAsync internally uses UserManager.GetUserId to retrieve the user id of the user which is then used to query the object from the user store (i.e. your database).
GetUserId basically looks like this:
public string GetUserId(ClaimsPrincipal principal)
{
return principal.FindFirstValue(Options.ClaimsIdentity.UserIdClaimType);
}
So this returns the claim value of Options.ClaimsIdentity.UserIdClaimType. Options is the IdentityOptions object that you configure Identity with. By default the value of UserIdClaimType is ClaimTypes.NameIdentifier, i.e. "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier".
So when you try to use UserManager.GetUserAsync(HttpContext.User), where that user principal has a UserID claim, the user manager is simply looking for a different claim.
You can fix this by either switchting to the ClaimTypes.NameIdentifier:
new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
})
Or you configure Identity properly so it will use your UserID claim type:
// in Startup.ConfigureServices
services.AddIdentity(options => {
options.ClaimIdentity.UserIdClaimType = "UserID";
});
Source:
https://stackoverflow.com/a/51122850/3850405
The claims from an external provider will be specific to that provider. It is not logging into the local identity store in your app, it is just claiming to know who the user is. So you need to log the user to your store (SignInManager) before you can use it for authorization. If you don't care about protecting resources and just want to know the user you can directly map to your internal store
The claims in the header need to be intercepted by the ASPNET 'middleware' using an authentication provider which will then set the User object in the HttpContext. Once you have the user, you would need to map your local user store to those from the school account, then get the claims as a separate call from the result. Usually the email is the subject claim and can be used for mapping:
var userName = User.Identity.Name;
var user = _userManager.FindByNameAsync(userName);
var claims = _userManager.GetClaimsAsync(user);
I have an ASP.NET Core 2.1 web application and am adding forgot password functionality. I have looked at several examples, and they seem to take one of two approaches. The first approach is to include either the user id or the user's email in the password reset url along with the password reset token. The second approach is to include only the password reset token in the password reset url and then require the user to enter identifying information (such as email) when attempting to change the password (Binary Intellect example). Is there a way to look up the user given just the password reset token?
My team lead has asked me to pass just the token in the password reset url and then look up the user. My initial research makes me believe that I would have to manually keep record of the user id and token relationship, but am hoping that there's something built in. I have reviewed the ASP.NET Core UserManager documentation, but did not find any methods for retrieving a user for a given token.
Here's some of the example code embedding the user id in the password reset URL (Microsoft Password Recovery Doc):
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
There is a way to get the UserId from the password reset token, but in my opinion it's tricky and a lot of work.
What are the defaults
If you have some codes like the following,
services.AddIdentity<AppUser, AppRole>(options =>
{
...
}
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
the last line .AddDefaultTokenProviders() adds 4 default token providers, which are used to generate tokens for reset passwords, change email and change phone number options, and for two factor authentication token generation, into the pipeline:
DataProtectorTokenProvider
PhoneNumberTokenProvider
EmailTokenProvider
AuthenticatorTokenProvider
The first one, DataProtectorTokenProvider, is what we're looking for. It uses data protection to serialize/encrypt those tokens.
And within the DataProtectorTokenProvider, its protector is default to the name of "DataProtectorTokenProvider".
How tokens are generated
If you look at GenerateAsync() method inside DataProtectorTokenProvider, you can kind of tell the token consists of:
Utc timestamp of the token creation (DateTimeOffset.UtcNow)
userId
Purpose string
Security stamp, if supported
The generate method concatenates all those, transform them to a byte array, and calls the protector inside to protect/encrypt the payload. Finally the payload is converted to a base 64 string.
How to get User Id
To get the userId from a token, you need to do the reverse engineering:
Convert the token from base 64 string back to the byte array
Call the protector inside to unprotect/decrypt the byte array
Read off the Utc timestamp
Read userId
The tricky part here is how to get the same DataProtector used to generate those token!
How to get the default Data Protector
Since the default DataProtectorTokenProvider is DIed into the pipeline, the only way I can think of to get the same DataProtector is to use the default DataProtectorTokenProvider to create a protector with the same default name, "DataProtectorTokenProvider", used to generate tokens!
public class GetResetPasswordViewModelHandler : IRequestHandler<...>
{
...
private readonly IDataProtector _dataProtector;
public GetResetPasswordViewModelHandler(...,
IDataProtectionProvider dataProtectionProvider)
{
...
_dataProtector = dataProtectionProvider.CreateProtector("DataProtectorTokenProvider");
// OR
// dataProtectionProvider.CreateProtector(new DataProtectionTokenProviderOptions().Name);
}
public async Task<ResetPasswordViewModel> Handle(GetResetPasswordViewModel query, ...)
{
// The password reset token comes from query.ResetToken
var resetTokenArray = Convert.FromBase64String(query.ResetToken);
var unprotectedResetTokenArray = _dataProtector.Unprotect(resetTokenArray);
using (var ms = new MemoryStream(unprotectedResetTokenArray))
{
using (var reader = new BinaryReader(ms))
{
// Read off the creation UTC timestamp
reader.ReadInt64();
// Then you can read the userId!
var userId = reader.ReadString();
...
}
}
...
}
}
Screenshot:
My 2 cents
It seems like it's a lot of work just try to read the userId off a password reset token. I understand your team lead probably doesn't want to expose the user id on the password reset link, or (s)he thinks it's redundant since the reset token has the userId.
If you're using integer to represent the userId and don't want to expose that to public, I would change it to GUID.
If you have to use integer as your userId, I would just create a column of the type unique_identifier off the user profile (I would call it PublicToken) and use that to identifier a user for all public matters.
var callbackUrl = Url.Action("resetPassword", "account", new
{
area = "",
rt = passwordResetToken, // reset token
ut = appUser.Id // user token, use GUID user id or appUser.PublicToken
}, protocol: Request.Scheme);
I believe there is no way you can do that you can pass user email then find it look for user in your code
public async Task<IActionResult> ResetPassword([FromBody]ResetPasswordViewModel model)
{
if (string.IsNullOrEmpty(model.Token) || string.IsNullOrEmpty(model.Email))
{
return RedirectToAction("Index", "Error", new { statusCode = AppStatusCode.NotFound });
}
var isResetTokenValid = await _userManager.CheckValidResetPasswordToken(model.Token, model.Email);
if (!isResetTokenValid || string.IsNullOrEmpty(model.Email))
{
return StatusCode(AppStatusCode.ResetPassTokenExpire);
}
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
return Ok();
}
await _userManager.ResetPasswordAsync(user, model.Token, model.Password);
return Ok();
}
You can view the implementaion detail here
What I do in this case is I keep that new token in a cache or sql table with user id in it. That way you first query that table containing reset token, validate it if you need it and get user.
I was trying to create a user in Azure AD without mail filed is user created successfully. I need to add the email id in Azure AD at the time of user created.
I added the mail property in json and it says
Property 'mail' is read-only and cannot be set.
My C# code is:
var url = string.Format("https://graph.windows.net/{0}/users?api-version=1.6",oauthsettings.TenantId);
var authDetails = _orchardServices.WorkContext.CurrentSite.As<AzureAuthenticationPart>();
var alogin = new AzureLogin();
var jwttoken = alogin.ServiceAuth(authDetails.ClientId, authDetails.ClientSecret);
var aadUser =new {
mail=email,
accountEnabled = true,
displayName = userName,
mailNickname = userName,
passwordProfile = new passwordProfile()
{
password = password,
forceChangePasswordNextLogin = authDetails.IsUpdatePwdNextLogin
},
userPrincipalName = userName + oauthsettings.DirectoryName,
};
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + jwttoken);
var modelval = Convert.ToString(JsonConvert.SerializeObject(aadUser));
var content = new StringContent(modelval, Encoding.UTF8, "application/json");
var result = client.PostAsync(url, content).Result;
Get Access Token from Azure AD After Login
JwtSecurityToken token = GetAccessToken(authDetails, code, returnUrl);
var claims = token.Claims;
return LogOn(claims, returnUrl);
Getting Email from JWT
public LogOnResponse LogOn(IEnumerable<System.Security.Claims.Claim> claims, string returnUrl)
{
var email = claims.FirstOrDefault(s => s.Type == "email").Value;
In this place I can't get the access token, because the user created time is not set the email in Graph API Request. I have another problem is this email id based only I was validate another site also, so I was required set the email in user created time.
I required email id for login in my application. i was integrate the Azure AD in existing application it's required for email.
Does anyone know how to set the email id in Azure AD for a user.
My Request in Postman. Response for Email Added in Request
Because the mail attribute is tied to Exchange Online, we don't permit you to write to that attribute unless you have an Exchange Online license. When you activate a license for the user, Exchange Online will update the field with the correct mailbox mail address during the creation of the user's mailbox. You can utilize "MailNickName" and " other emails" during the creation of a user. This field will also depend upon if it is a "local account (B2C)" or "work or school account".
I hope this answers your question concerning the "mail" attribute being "read-only"
There are two different fields for Email Addresses on an AAD User.
From the Graph API Reference:
mail
POST, GET ($filter)
The SMTP address for the user, for example, "jeff#contoso.onmicrosoft.com".
otherMails
POST, GET ($filter), PATCH
A list of additional email addresses for the user; for example: ["bob#contoso.com", "Robert#fabrikam.com"].
Note that you can only set the mail property when you initially create the user (POST), but you can update the otherMails property whenever you want (PATCH).
It seems like you should be using the otherMails property for your needs.
I have two servers that are using the same ASP.NET Core Identity backend. I generate the password reset token with the following:
var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser);
I send this token via an email link. When the user clicks the link, they are taken to a separate site which should provide a UI to change the password. The following code handles the user's password submission of both the token and their new password:
var identityResult = await _userManager.ResetPasswordAsync(applicationUser, code, password);
On the second server, the identity result always returns false because "invalid token".
Looking through the source, I see that the token is generated using the IP address (so I understand why the token validation failed).
My question is how do I enable successful token creation/validation across different machines? In previous forms of ASP.NET, I would likely use a shared machine key to prevent these scenarios. ASP.NET Core doesn't seem to have a similar concept. From what I've read, it seems that this might be a scenario to use the DataProtection API. Unfortunately, I haven't seen any examples as how to apply this to generating the reset token.
Have you tried setting the application name to the same value in both applications?
services.AddDataProtection().SetApplicationName("same for both apps");
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview
P.S - I'm struggling with exactly the same problem.
you should encode your token before you send it. You should do something like this:
var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser);
var encodedCode = HttpUtility.UrlEncode(token);
After encoding it, you must pass the encoded token rather than the generated token.
I faced the similar problem. Its not about 2 servers actually. Its about identity framework. You can derived from usermanager and you can override provider with central one. But I tried something different and it worked.
First of all ConfirmEmail method looks into database, if you have one database the shouldn't be a problem between tokens with more than one server.
In your usermanager you should create dataprovider at your constructor like this.
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
var dataProtectorProvider = Startup.DataProtectionProvider;
var dataProtector = dataProtectorProvider.Create("My Identity");
this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, string>(dataProtector);
//this.UserTokenProvider.TokenLifespan = TimeSpan.FromHours(24);
}
Also you should be see token in your database table for users. After this line of code.
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
UserManager.EmailService = new EmailService();
await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
When you see token in your database, check email for same. then click you callback url and correct the encode of url.
For using dataProtectorProvider ;
public partial class Startup
{
public static IDataProtectionProvider DataProtectionProvider { get; set; }
public void ConfigureAuth(IAppBuilder app)
{
DataProtectionProvider = app.GetDataProtectionProvider();
}
}