Add 2fa authenticator to user - c#

I have been trying to work out how to enable 2f login with Google Authentication in my Identity server 4 application.
2fa works fine with both email and phone.
if i check
var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user);
it has two email and phone. I am assuming that this would be the two factor providers that have been set up for this user.
Now if i check _usermanager again there is a field called tokenproviders. Which appears to contain default, email, phone, and authenticator. I assume these are the ones that Asp .net identity is configured to deal with.
I have worked out how to create the secret needed to genreate the QR code for the authecator app. As well has how to build the QR code and to test the code
var code = _userManager.GenerateNewAuthenticatorKey();
var qr = AuthencatorHelper.GetQrCodeGoogleUrl("bob", code, "My Company");
var user = await _signInManager.TwoFactorAuthenticatorSignInAsync(codeFromAppToTestWith, true, false);
if (user == null)
{
return View("Error");
}
Now the problem. I have gone though every method I can find on the user trying to work out how to add another token provider to the user.
How do I assign a new token provider to the user and supply the secret code needed to create the authentication codes?? I am not even seeing any tables in the database setup to handle this information. email and phone number are there and there is a column for 2faenabled. But nothing about authenticator.
I am currently looking into creating a custom usermanager and adding a field onto the application user. I was really hoping someone had a better idea.

From what I can see, you are generating a new authenticator key each time the user needs to configure an authenticator app:
var code = _userManager.GenerateNewAuthenticatorKey();
You should be aware that using GenerateNewAuthenticatorCodeAsync will not persist the key, and thus will not be useful for 2FA.
Instead, you need to generate and persist the key in the underlying storage, if it not already created:
var key = await _userManager.GetAuthenticatorKeyAsync(user); // get the key
if (string.IsNullOrEmpty(key))
{
// if no key exists, generate one and persist it
await _userManager.ResetAuthenticatorKeyAsync(user);
// get the key we just created
key = await _userManager.GetAuthenticatorKeyAsync(user);
}
Which will generate the key if not already done and persist it in the database (or any storage configured for Identity).
Without persisting the key inside the storage, the AuthenticatorTokenProvider will never be able to generate tokens, and will not be available when calling GetValidTwoFactorProvidersAsync.

Related

Firebase-Admin save user's Custom userPropery

I'm trying to make a server side api call that will get a firebase token and make a
setUserProperty
I managed to connect to firebaseAdmin and get the user , but I can't find how to update the user's custom field ,
I can only see user fields like email password etc...
This is what i got so far
FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromFile("firebase-adminsdk.json"),
});
// Then FirebaseAuth.DefaultInstance will give you the initialized SDK Auth instance.
// E.g.:
var fbUser= await FirebaseAuth.DefaultInstance.GetUserByPhoneNumberAsync("+91112324564");
UserRecordArgs args = new UserRecordArgs();
Any ideas if it's at all possible ? (I don't want to do it from the app)

ASP.NET Core custom login setup

I have a situation where we use the ASP.NET Core Identity Framework for the Intranet system with hooks into an old CRM database (this database can't be changed without monumental efforts!).
However, we're having customers login to a separate DBContext using identity framework, with an ID to reference back to the CRM. This is in a separate web app with shared projects between them.
This is cumbersome and causes issues when customers are merged in the CRM, or additional people are added to an account etc. Plus we do not need to use roles or any advanced features for the customer login.
So I was thinking to store the username and password in the CRM with the following process:
Generate a random random password.
Use the internal database ID as the salt.
Store the Sha256 hash of the "salt + password" in the password field.
When a customer logs in, we:
Check the Sha256 hash against the salt and given password
If successful, store a session cookie with the fact the customer is logged in: _session.SetString("LoggedIn", "true");
Each request to My Account will use a ServiceFilter to check for the session cookie. If not found, redirect to the login screen.
Questions:
Is this secure enough?
Should we generate a random salt? If stored in the customer table how would it be different to the internal (20 character) customer ID?
Is there a way for the server session cookie to be spoofed? Should we store a hash in the session which we also check on each action?
Is this secure enough?
Generally roll-your-own security is a bad idea because it won't have faced as much scrutiny as an industry standard like Identity Framework. If your application is not life-or-death then maybe this is enough.
Should we generate a random salt?
Yes, salts should always be random. One reason is that when a user changes their password, back to a previous password, if the salt is constant too, then you would get the same hash again, which could be detected.
Another reason is that we don't want the salts to be predictable or sequential. That would make it easier for hackers to generate rainbow tables.
If stored in the customer table how would it be different to the
internal (20 character) customer ID?
I suppose if your customer ID is already a long random guid then that might not matter exactly, but best to play it safe, with cryptographically random disposable salts.
Look at solutions which use RNGCryptoServiceProvider to generate the salt.
Is there a way for the server session cookie to be spoofed?
I don't think a hacker could create a new session just by spoofing. They would need the username & password.
But they could highjack an existing session using Cross-Site Request Forgery.
Should we store a hash in the session which we also check on each
action?
I don't think that would help. Your _session.SetString("LoggedIn", "true") value is already stored on the server and is completely inaccessible from the client. The client only has access to the session cookie, which is just a random id. If that LoggedIn session value is true, then a hash wouldn't make it extra true.
Last year I made a custom IUserPasswordStore for a customer. This solution involved Microsoft.AspNetCore.Identity.UserManager which handles password hashing behind the scenes, no custom password handling required. You will be responsible for storing hashed password in db along with other user properties.
I cannot publish the code in its entirety, it is not my property, but I can sketch up the main parts.
First, we need the IdentityUser:
public class AppIdentityUser : IdentityUser<int>
{
public string Name { get; set; }
}
Then an implementation if IUserPasswordStore
public class UserPasswordStore : IUserPasswordStore<AppIdentityUser>
{
private readonly IUserRepo _userRepo; // your custom user repository
private readonly IdentityErrorDescriber _identityErrorDescriber;
public UserPasswordStore(IUserRepo userRepo, IdentityErrorDescriber identityErrorDescriber)
{
_userRepo = userRepo;
_identityErrorDescriber = identityErrorDescriber;
}
public Task<IdentityResult> CreateAsync(AppIdentityUser user, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
// if email exists, fail
if (_userRepo.GetByEmailAddress(user.Email) != null)
{
return Task.FromResult(IdentityResult.Failed(_identityErrorDescriber.DuplicateEmail(user.Email)));
}
// ... convert AppIdentityUser to model class
//
_userRepo.Save(userModel);
return Task.FromResult(IdentityResult.Success);
}
... implementation of the rest of IUserPasswordStore<AppIdentityUser> comes here
}
Inject this into code for identity user CRUD-operations, e.g. user management controller:
UserManager<AppIdentityUser>
Sample code for changing password (sorry for the nesting)
var result = await _userManager.RemovePasswordAsync(identityUser);
if (result.Succeeded)
{
result = await _userManager.AddPasswordAsync(identityUser, model.Password);
if (result.Succeeded)
{
var updateResult = await _userManager.UpdateAsync(identityUser);
if (updateResult.Succeeded)
{
... do something
}
}
}
Inject this into LoginController:
SignInManager<AppIdentityUser>
We also need an implementation of
IRoleStore<IdentityRole>.
If authorization is not required, leave all methods empty.
In Startup#ConfigureServices:
services.AddIdentity<AppIdentityUser, IdentityRole>().AddDefaultTokenProviders();
services.AddTransient<IUserStore<AppIdentityUser>, UserPasswordStore>();
services.AddTransient<IRoleStore<IdentityRole>, RoleStore>();
services.Configure<CookiePolicyOptions>(options => ...
services.Configure<IdentityOptions>(options => ...
services.ConfigureApplicationCookie(options => ...
In Startup#Configure:
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
See also https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-custom-storage-providers?view=aspnetcore-3.1

Prompt user for additional information during an Open Id Connect event?

Using asp.net Core, Mvc and OpenIdConnect, is it possible to prompt an authenticated user for additional information during the ODIC authentication process, and then redirect back to the originally-desired page?
To give a concrete example: in our system one person, represented by an email address, can have multiple user ids that they may wish to operate under. Assume my email address is tregan#domain.com, and I have 3 user ids to choose from: treganCat, treganDog, treganMouse. When I hit a Controller action that is decorated with the [Authorize] attribute I first go through OpenIdConnect authentication, and one of the claims returned is an email address.
Using that email address, I want the application to prompt me to select the identity that I want to run under (treganDog, treganCat, or treganMouse).
From there, I want the application to take the user id that I selected, interrogate a database for the roles that go along with the selected user id, and load those roles as claims to my identity.
Finally, I want the application to send me on to my desired page (which is the protected Controller method that I originally attempted to visit).
Is this possible?
I'm using an Owin Startup class; the code below "works" except for the fictional line "var identityGuid = [return value from the prompt];" ("fictional" because it represents what I would like to occur, but in fact a series of redirects would be needed).
My example below uses the OnTicketReceived event, but that selection is arbitrary, I would be willing to do this in any event.
services.AddAuthentication(authenticationOptions =>
{
authenticationOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
authenticationOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(openIdConnectOptions =>
{
openIdConnectOptions.Authority = Configuration["PingOne:Authority"];
openIdConnectOptions.CallbackPath = "/Callback";
openIdConnectOptions.ClientId = Configuration["PingOne:ClientId"];
openIdConnectOptions.ClientSecret = Configuration["PingOne:ClientSecret"];
openIdConnectOptions.ResponseType = "code";
openIdConnectOptions.Events.OnTicketReceived = (ticketReceivedContext) =>
{
var emailClaim =
ticketReceivedContext.Principal.Claims.FirstOrDefault(o =>
o.Type == ClaimTypes.Email);
string emailAddress = emailClaim.Value;
//here is where I would like to prompt the user to select an identity based on the email address
//the selected identity is represented by a guid
var identityGuid = [return value from the prompt];
var roles = new MyRepository(myContext).GetRolesForUserId(identityGuid);
var claims = new List<Claim>();
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
ticketReceivedContext.Principal.AddIdentity(new ClaimsIdentity(claims));
return Task.CompletedTask;
};
});
This is impersonation where there is a real user and you need to identify the impersonated user after login.
You will need to complete the login first, return to the app and configure the principal. Then render a UI and receive the selected choice.
You then need your UI to call the back end and tell it to update claims in the auth cookie. Not sure if you'll get this to work though - the impersonated user may need separate storage - such as a second cookie.
This highlights that it can be useful to separate the token / credential the UI receives from the claims the back end works with.
I use the below design a lot for REST APIs that serve UIs directly - though it may be overkill for your solution:
https://authguidance.com/2017/10/03/api-tokens-claims/
I think what I want to do is simply not possible without either figuring out a way to do it inside PingOne or writing my own IdentityServer and taking care of the extra steps there.
I decided to instead write a custom middleware that fires after the Authentication middleware, as described in this SO question: In asp.net core, why is await context.ChallengeAsync() not working as expected?

Force ASP.Net claims validation after they have been changed

I'm developing an ASP.Net application with OWIN and currently I have a problem with claims. I have two levels of application: basic and advanced. Some features are available for advanced users only. So I check claims, and if user doesn't have claim advanced I return 403. But here I found the workaround which ruins this system:
User activates advanced mode
He performs any action and save its access token
He disactivates advanced mode
Now he's able to perform actions just like he is in advanced mode with this token, however he actually has not permissions to do it.
I'm trying to find some fine solution for this situation but I have no ideas except set 1 minute timeout or always check AspNetUserClaims instead of cookie and so on, but they don't work in my case because he can activate a lifetime feature in this one minute interval and then use it forever.
But i'd like to set some server-side flag like oops, this guy have just changed his cookies, check it from database or something to lower database roundtrips for common API calls.
Is there any standard default way to do it? Or maybe I have just chosen a wrong instrument?
You Need to send update cookies according to your claim value.
Below is code to update your claim value.
Inside your action when user disable/enable advanced mode, Then update user claims.
var isAdvanced= "1";
var identity = (ClaimsIdentity)User.Identity;
// check if claim exist or not.
var existingClaim = identity.FindFirst("IsAdvanced");
if (existingClaim != null)
identity.RemoveClaim(existingClaim);
// add/update claim value.
identity.AddClaim(new Claim("IsAdvanced", isAdvanced));
IOwinContext context = Request.GetOwinContext();
var authenticationContext = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);
if (authenticationContext != null)
{
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(identity,authenticationContext.Properties);
}
As soon as you will made a redirection, you will get your get updated claim value, hence you don't need to make database round trip.
Credit to this post.
Unfortunly, the only way I found is actually query DB itself and check if user has valid credentials:
public bool HasRequiredClaims(string[] requiredClaims)
{
using (var context = new ApplicationDbContext())
{
int actualNumberOfClaims = context.Users
.SelectMany(x => x.Claims)
.Count(c => requiredClaims.Contains(c.ClaimValue)); // claim values are unique per user (in my case) so I don't have to filter on user
return actualNumberOfClaims == claimsValuesToSearch.Length;
}
}

How to get user name, email, etc. from MobileServiceUser?

After a lot of digging around I've got my WPF application signing users in via Azure Mobile Service. My Mobile Service is connected to an Azure Active Directory that I have set up. However, when I log the user in with MobileServiceClient.LoginAsync(...) the MobileServiceUser UserId is in an unreadable hash it seems. For example it looks like: "Aad:X3pvh6mmo2AgTyHdCA3Hwn6uBy91rXXXXXXXXXX". What exactly is this?
I'd like to grab the user's display name to use but I can't figure out how.
That is the userID of Azure Active Directory. You need to create a service to expose your AAD info through a service and retrieve the additional information using the access token you get from your user.
First:
ServiceUser user = this.User as ServiceUser;
var identities = await user.GetIdentitiesAsync();
var aad = identities.OfType<AzureActiveDirectoryCredentials>().FirstOrDefault();
var aadAccessToken = aad.AccessToken;
var aadObjectId = aad.ObjectId;
This will give you the access token and objectID , then you need to query the information through AAD graphy API.
https://msdn.microsoft.com/library/azure/dn151678.aspx
Look at the sample request part. You should provide the query with the access token you got and objectId.
Here is an alternative approach, after reading http://justazure.com/azure-active-directory-part-2-building-web-applications-azure-ad/ scroll to the section on Identity in .Net it talks how claims are a standard part of the framework. So once you get the credentials object like provided by #beast
var aad = identities.OfType<AzureActiveDirectoryCredentials>().FirstOrDefault();
You can actually grab a dictionary with the various properties. Examples of some the properties can be found at https://msdn.microsoft.com/en-us/library/system.identitymodel.claims.claimtypes(v=vs.110).aspx
So from there you can do the following:
if (aad != null)
{
var d = aad.Claims;
var email = d[ClaimTypes.Email];
}
I did this to pull the user id which was cross referenced in a table. FYI I am using App Service, but I believe the credentials object is the same in Mobile Service

Categories