For some reason EmailCode is not showing up in the valid two factor authentication providers. PhoneCode however did up until I removed it, now nothing shows up at all. I have debugged and it shows up under the UserManager, but for some odd reason GetValidTwoFactorProvidersAsync doesn't retrieve it. I've already attempted to manually add it by bypassing the method and retrieving the value manually, but then it throws the error message that the Microsoft.AspNet.Identity.EmailTokenProvider does not exist. I am at a loss to explain why this isn't working.
public async Task<ActionResult> SendCode(string returnUrl)
{
var userId = await SignInManager.GetVerifiedUserIdAsync();
if (userId == null)
{
return View("Error");
}
var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId);
var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl });
}
Identityconfig
manager.RegisterTwoFactorProvider("EmailCode", new Microsoft.AspNet.Identity.EmailTokenProvider<SystemUser>
{
Subject = "SecurityCode",
BodyFormat = "Your security code is {0}"
});
manager.EmailService = new EmailService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<SystemUser>(
dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
Make sure you have a ConfirmedEmail for the User. You can look up the user in the database and look at the EmailConfirmed flag to see if it is set or not.
Since I can't see your codes and hence can't suggest a proper fix but I can tell you how I did it and all went okay. It came very handy and easy for me when I tried configuring the application for 2 way authentication using these links:
http://www.asp.net/identity/overview/features-api/two-factor-authentication-using-sms-and-email-with-aspnet-identity
http://typecastexception.com/post/2014/04/20/ASPNET-MVC-and-Identity-20-Understanding-the-Basics.aspx
Suggest you to try them if you haven't yet. Hope it helps!
Related
Long story short, no matter what I try VeraCode continues to flag 8 lines of my code as flaws with CWE 918. This is old code so I'm not sure why it's suddenly being flagged.
Here's an example [offending] method with the flagged line in bold
public virtual async Task<HttpResponseMessage> Put(string controller = "", Dictionary<string, object> parameters = null, object body = null)
{
if (string.IsNullOrWhiteSpace(ApiBaseUrl)) return null;
HttpResponseMessage response = null;
using (var client = GetHttpClient())
{
client.BaseAddress = new Uri(ApiBaseUrl);
if (!string.IsNullOrEmpty(Token)) client.DefaultRequestHeaders.Add("Token-Key", Token);
if (!string.IsNullOrEmpty(DeviceId)) client.DefaultRequestHeaders.Add("DeviceId", DeviceId);
var url = GenerateUrl(controller, parameters);
var requestBody = GeneratedHttpContent(body);
if (requestBody == null) requestBody = new StringContent("");
**response = await client.PutAsync(url, requestBody);**
await LogError(response);
return response;
}
}
Here's my proposed fix that utilized an extension method to validate the URL
var url = GenerateUrl(controller, parameters);
var requestBody = GeneratedHttpContent(body);
if (requestBody == null) requestBody = new StringContent("");
**if (url.IsValidUrl())
{
response = await client.PutAsync(url, requestBody);
}
else
{
response = new HttpResponseMessage(HttpStatusCode.BadRequest);
}**
await LogError(response);
return response;
Here is the extension method with a VeraCode attribute
[RedirectUrlCleanser]
public static bool IsValidUrl(this string source)
{
return Uri.TryCreate(source, UriKind.RelativeOrAbsolute, out Uri uriResult) && Uri.IsWellFormedUriString(source, UriKind.RelativeOrAbsolute);
}
I can have VeraCode automatically mitigate based on the attribute, but our client will be performing their own scan and certainly won't have that setting enabled.
Any ideas on how I can resolve this would be appreciated.
The true source of the flaw is inside of your GenerateUrl method which is unfortunately not shown, but here is the general idea of what the Veracode is complaining about.
For CWE ID 918 it is hard to make Veracode recognize your fix unless you have static URL. You need to validate all your inputs that become parts of your request URL.
Below is what I found at the Veracode site:
https://community.veracode.com/s/question/0D52T00004i1UiSSAU/how-to-fix-cwe-918-veracode-flaw-on-webrequest-getresponce-method
The complete solution existed only for the case where you have single or some small number of possible input values (white list):
public WebResponse ProxyImage(string image_host, string image_path)
{
string validated_image_host = AllowedHosts.Host1;
if (image_host.Equals(AllowedHosts.Host2))
validated_image_host = AllowedHosts.Host2;
string validated_image = AllowedImages.Image1;
if (image_path.Equals(AllowedImages.Image2))
validated_image = AllowedImages.Image2;
string url = $"http://{validated_image_host}.example.com/{validated_image}";
return WebRequest.Create(url).GetResponse();
}
If the set of possible valid values is too large for that kind of validation then you need to fix the flaw by implementing dynamic validation of inputs using regular expressions. Unfortunately, Veracode is not smart enough to recognize that kind of fix, so "mitigation by design" is still required.
public WebResponse ProxyImage(string image_host, string image_path)
{
var image_host_regex = new System.Text.RegularExpressions.Regex("^[a-z]{1,10}$");
if (!image_host_regex.Match(image_host).Success)
throw new ArgumentException("Invalid image_host");
var image_path_regex = new System.Text.RegularExpressions.Regex("^/[a-z]{1,10}/[a-z]{1,255}.png$");
if (!image_path_regex.Match(image_path).Success)
throw new ArgumentException("Invalid image_host");
string url = $"http://{image_host}.example.com/{image_path}";
return WebRequest.Create(url).GetResponse();
}
Another way to fix this issue (which is kind of a hack) is to append your query string parameters in the baseAddress of the HttpClient, this way the veracode will not treat it like a flaw.
Here is how the solution would look like
public async Task<Data> GetData(string input)
{
try
{
var httpClient = new HttpClient();
//Appended the parameter in base address to
//to fix veracode flaw issue
httpClient.BaseAddress = new Uri($"https://someurl.com/somefunction/{input}");
//passing empty string in GetStringAsync to make sure
//veracode doesn't treat it like modifying url
var content = await httpClient.GetStringAsync("");
return JsonConvert.DeserializeObject<Data>(content);
}
}
I am trying to create a user by calling register function within the application.
This function works fine when the function is called as a API.
When Called from Inside the application it throws an error
AccountController ac = new AccountController();
RegisterBindingModel rbm = new RegisterBindingModel();
rbm.Email = UserAccountBase.Email;
rbm.Password = "TestPassword";
rbm.ConfirmPassword = "TestPassword";
var userId = await ac.Register(rbm);
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
try
{
var result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
}
catch(Exception e) {
var message = e.Message;
}
return Ok(user.Id);
}
UserManager.CreateAsync Throws an error
Value cannot be null. Parameter name: request
Did you build this using ASP Identity 2.0 and EF Code First? If so, check to see if your initial DB configuration put a column called 'Discriminator' at the end of AspNetUsers. If this is the case, recreate an initial migration and remove that column that gets generated. Once removed, you can perform an update-database. Does this make any sense? I just had this happen to me.
In the identity model, you will have a Discriminator column. In your model, add migration and update the database.
I have searched over the web and could not find a solution to my problem. I am implementing OAuth in my app. I am using ASP .NET Web API 2, and Owin. The scenario is this, once a user request to the Token end point, he or she will receive an access token along with a refresh token to generate a new access token. I have a class the helps me to generate a refresh token. Here is it :
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var refreshTokenId = Guid.NewGuid().ToString("n");
using (AuthRepository _repo = new AuthRepository())
{
var refreshTokenLifeTime = context.OwinContext.Get<string> ("as:clientRefreshTokenLifeTime");
var token = new RefreshToken()
{
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(15)
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddRefreshToken(token);
if (result)
{
context.SetToken(refreshTokenId);
}
}
}
// this method will be used to generate Access Token using the Refresh Token
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
string hashedTokenId = Helper.GetHash(context.Token);
using (AuthRepository _repo = new AuthRepository())
{
var refreshToken = await _repo.FindRefreshToken(hashedTokenId);
if (refreshToken != null )
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
// one refresh token per user and client
var result = await _repo.RemoveRefreshToken(hashedTokenId);
}
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
now i am allowing my users to register through facebook. Once a user register with facebook, I generate an access token and give it to him. Should I generate a refresh token as well ? Onething comes to my mind, is to generate a long access token like one day, then this user has to login with facebook again. But if i do not want to do that, I can give the client, a refresh token, and he can use it to refresh the generated access token and get a new. How do I create the refresh token and attach it to the response when someone register or login with facebook or externally ?
Here is my external registration API
public class AccountController : ApiController
{
[AllowAnonymous]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var accessTokenResponse = GenerateLocalAccessTokenResponse(model.UserName);
return Ok(accessTokenResponse);
}
}
// Private method to generate access token
private JObject GenerateLocalAccessTokenResponse(string userName)
{
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("userName", userName),
new JProperty("access_token", accessToken),
// Here is what I need
new JProperty("resfresh_token", GetRefreshToken()),
new JProperty("token_type", "bearer"),
new JProperty("refresh_token",refreshToken),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
return tokenResponse;
}
I spent a lot of time to find the answer to this question. So, i'm happy to help you.
1) Change your ExternalLogin method.
It usually looks like:
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
Now, actually, it is necessary to add refresh_token.
Method will look like this:
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
// ADD THIS PART
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =
new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
Now the refrehs token will be generated.
2) There is a problem to use basic context.SerializeTicket in SimpleRefreshTokenProvider CreateAsync method.
Message from Bit Of Technology
Seems in the ReceiveAsync method, the context.DeserializeTicket is not
returning an Authentication Ticket at all in the external login case.
When I look at the context.Ticket property after that call it’s null.
Comparing that to the local login flow, the DeserializeTicket method
sets the context.Ticket property to an AuthenticationTicket. So the
mystery now is how come the DeserializeTicket behaves differently in
the two flows. The protected ticket string in the database is created
in the same CreateAsync method, differing only in that I call that
method manually in the GenerateLocalAccessTokenResponse, vs. the Owin
middlware calling it normally… And neither SerializeTicket or
DeserializeTicket throw an error…
So, you need to use Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer to searizize and deserialize ticket.
It will be look like this:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer
= new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
token.ProtectedTicket = System.Text.Encoding.Default.GetString(serializer.Serialize(context.Ticket));
instead of:
token.ProtectedTicket = context.SerializeTicket();
And for ReceiveAsync method:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
context.SetTicket(serializer.Deserialize(System.Text.Encoding.Default.GetBytes(refreshToken.ProtectedTicket)));
instead of:
context.DeserializeTicket(refreshToken.ProtectedTicket);
3) Now you need to add refresh_token to ExternalLogin method response.
Override AuthorizationEndpointResponse in your OAuthAuthorizationServerProvider. Something like this:
public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
{
var refreshToken = context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["refresh_token"];
if (!string.IsNullOrEmpty(refreshToken))
{
context.AdditionalResponseParameters.Add("refresh_token", refreshToken);
}
return base.AuthorizationEndpointResponse(context);
}
So.. thats all! Now, after calling ExternalLogin method, you get url:
https://localhost:44301/Account/ExternalLoginCallback?access_token=ACCESS_TOKEN&token_type=bearer&expires_in=300&state=STATE&refresh_token=TICKET&returnUrl=URL
I hope this helps)
#giraffe and others offcourse
A few remarks. There's no need to use the custom tickerserializer.
The following line:
Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =
new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
As tokenformat: Startup.OAuthOptions.AccessTokenFormat is used. Since we want to provide a refeshtoken this needs te be changed to: Startup.OAuthOptions.RefreshTokenFormat
Otherwise if you want to get a new accesstoken and refresh the refreshtoken ( grant_type=refresh_token&refresh_token=...... ) the deserializer/unprotector will fail. Since it uses the wrong purposes keywords at the decrypt stage.
Finally found the solution for my problem.
First of all, if you EVER encounter any problems with OWIN and you cannot figure out what is going wrong, I advise you to simply enable symbol-debugging and debug it. A great explanation can be found here:
http://www.symbolsource.org/Public/Home/VisualStudio
My mistake simply was, that I was calculating a wrong ExiresUtc when using external login providers. So my refreshtoken basically was always expired right away....
If you are implementing refresh tokens, then look at this gread blog article:
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
And to make it work with refresh tokens for external providers, you have to set the two requried parameters ("as:clientAllowedOrigin" and "as:clientRefreshTokenLifeTime") on the context
so instead of
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var context = new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
you need to get the client first and set the context parameters
// retrieve client from database
var client = authRepository.FindClient(client_id);
// only generate refresh token if client is registered
if (client != null)
{
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var context = new AuthenticationTokenCreateContext(Request.GetOwinContext(), AuthConfig.OAuthOptions.RefreshTokenFormat, ticket);
// Set this two context parameters or it won't work!!
context.OwinContext.Set("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
await AuthConfig.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
}
I'm trying to retrieve user properties that are returned as the OnAuthenticated context and added as a claims following this example: How to access Facebook private information by using ASP.NET Identity (OWIN)?
I can see that data I am expecting is being returned at login and is being added as a Claim within Starup.Auth.cs. But, when I am within the Account Controller, the only claims that appears within the UserManager or UserStore is issued by LOCAL AUTHORITY. No claims can be found for Facebook (or other external providers). Where do the claims added to context end up? (I'm using VS2013 RTM.)
Full source and live site on Azure linked here: https://github.com/johndpalm/IdentityUserPropertiesSample/tree/VS2013rtm
Here is what I have in Startup.Auth.cs:
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
foreach (var x in context.User)
{
var claimType = string.Format("urn:facebook:{0}", x.Key);
string claimValue = x.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));
}
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
return Task.FromResult(0);
}
}
};
facebookOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookOptions);
An alternative way to capture the external login properties would be to add a single claim for the access token and populate it with properties:
const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions
{
AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
var claim = new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook");
foreach (var x in context.User)
{
string key = string.Format("urn:facebook:{0}", x.Key);
string value = x.Value.ToString();
claim.Properties.Add(key, value);
}
context.Identity.AddClaim(claim);
return Task.FromResult(0);
}
}
};
NOTE - This sample does not work: Though it would be nice to pass a single claim with properties. The external cookie seems to note honor the claims properties. The properties are empty when retrieving them later from the identity.
I was able to create a working example, using MVC 5 RTM templates, OWIN, and ASP.NET Identity bits. You can find the complete source and a link to a live working example here: https://github.com/johndpalm/IdentityUserPropertiesSample
Here's what worked for me:
Create a new (insert provider name here) AuthenticationOptions object in Startup.ConfigureAuth (StartupAuth.cs), passing it the client id, client secret, and a new AuthenticationProvider. You will use a lambda expression to pass the OnAuthenticated method some code to add Claims to the identity which contain the values you extract from context.Identity.
StartUp.Auth.cs
// Facebook : Create New App
// https://dev.twitter.com/apps
if (ConfigurationManager.AppSettings.Get("FacebookAppId").Length > 0)
{
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
foreach (var x in context.User)
{
var claimType = string.Format("urn:facebook:{0}", x.Key);
string claimValue = x.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));
}
return Task.FromResult(0);
}
}
};
app.UseFacebookAuthentication(facebookOptions);
}
NOTE: The Facebook auth provider works with the code used here. If you use this same code with the Microsoft Account provider (or Foursquare provider I created using the MS account code as a model), it fails to login. If you select just the access_token parameter, it works fine. Seems like some parameters break the login process. (An issue has been opened on katanaproject.codeplex.com if progress on this is of interest to you.) I'll update if I find the cause. I didn't do much with Twitter or Google beyond verifying that I could get the access_token.
var msaccountOptions = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationOptions()
{
ClientId = ConfigurationManager.AppSettings.Get("MicrosoftClientId"),
ClientSecret = ConfigurationManager.AppSettings.Get("MicrosoftClientSecret"),
Provider = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:microsoftaccount:access_token", context.AccessToken, XmlSchemaString, "Microsoft"));
return Task.FromResult(0);
}
}
};
app.UseMicrosoftAccountAuthentication(msaccountOptions);
In AccountController, I extract the ClaimsIdentity from the AuthenticationManager using the external cookie. I then add it to the identity created using the application cookie. I ignored any claims that starts with "...schemas.xmlsoap.org/ws/2005/05/identity/claims" since it seemed to break the login.
AccountController.cs
private async Task SignInAsync(CustomUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// Extracted the part that has been changed in SignInAsync for clarity.
await SetExternalProperties(identity);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
private async Task SetExternalProperties(ClaimsIdentity identity)
{
// get external claims captured in Startup.ConfigureAuth
ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (ext != null)
{
var ignoreClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims";
// add external claims to identity
foreach (var c in ext.Claims)
{
if (!c.Type.StartsWith(ignoreClaim))
if (!identity.HasClaim(c.Type, c.Value))
identity.AddClaim(c);
}
}
}
And finally, I want to display whatever values are not from the LOCAL AUTHORITY. I created a partial view _ExternalUserPropertiesListPartial that appears on the /Account/Manage page. I get the claims I previously stored from AuthenticationManager.User.Claims and then pass it to the view.
AccountController.cs
[ChildActionOnly]
public ActionResult ExternalUserPropertiesList()
{
var extList = GetExternalProperties();
return (ActionResult)PartialView("_ExternalUserPropertiesListPartial", extList);
}
private List<ExtPropertyViewModel> GetExternalProperties()
{
var claimlist = from claims in AuthenticationManager.User.Claims
where claims.Issuer != "LOCAL AUTHORITY"
select new ExtPropertyViewModel
{
Issuer = claims.Issuer,
Type = claims.Type,
Value = claims.Value
};
return claimlist.ToList<ExtPropertyViewModel>();
}
And just to be thorough, the view:
_ExternalUserPropertiesListPartial.cshtml
#model IEnumerable<MySample.Models.ExtPropertyViewModel>
#if (Model != null)
{
<legend>External User Properties</legend>
<table class="table">
<tbody>
#foreach (var claim in Model)
{
<tr>
<td>#claim.Issuer</td>
<td>#claim.Type</td>
<td>#claim.Value</td>
</tr>
}
</tbody>
</table>
}
Again, the working example and complete code is on GitHub: https://github.com/johndpalm/IdentityUserPropertiesSample
And any feedback, corrections, or improvements would be appreciated.
So this article explains how this all works pretty well: Decoupling owin external auth
But the short answer is, when you get authenticated from facebook, that is giving you an external identity. You then need to take that external identity and 'sign in' a local app identity, its in that stepthat you need to add any claims you want from the external identity to the ClaimsIdentity that becomes User.Identity.
Edit: To clarify further, you could do it inside of ExternalLoginCallback:
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl) {
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null) {
return RedirectToAction("Login");
}
// Sign in this external identity if its already linked
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null) {
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent) {
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
So you will need to pass in extra data to the SignIn, which will look something like this:
ClaimsIdentity id = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
This ClaimsIdentity will have your added claim, and you will need to add that claim to the identity created in the SignInAsync method for it to show up.
In short the line that is required once AddClaim is used is as follows:
Taken from johns answer above.
ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
I am trying to get to grips with the new Membership system introduced in ASP.NET MVC 5 and I've come across a small issue which I am pretty sure you will be able to help me with.
I am going based off this tutorial and have introduced custom properties to ApplicationUser such as Name, Surname, DOB, etc.
However, instead of creating the user, I am trying to update the currently logged in one. I am looking at the controller method which is currently used to change password.
public async Task<ActionResult> Manage(ManageUserViewModel model)
{
string userId = User.Identity.GetUserId();
bool hasLocalLogin = await IdentityManager.Logins.HasLocalLoginAsync(userId);
ViewBag.HasLocalPassword = hasLocalLogin;
ViewBag.ReturnUrl = Url.Action("Manage");
if (hasLocalLogin)
{
if (ModelState.IsValid)
{
IdentityResult result = await IdentityManager.Passwords.ChangePasswordAsync(User.Identity.GetUserName(), model.OldPassword, model.NewPassword);
if (result.Success)
{
return RedirectToAction("Manage", new { Message = "Your password has been changed." });
}
else
{
AddErrors(result);
}
}
}
else
{
// User does not have a local password so remove any validation errors caused by a missing OldPassword field
ModelState state = ModelState["OldPassword"];
if (state != null)
{
state.Errors.Clear();
}
if (ModelState.IsValid)
{
// Create the local login info and link it to the user
IdentityResult result = await IdentityManager.Logins.AddLocalLoginAsync(userId, User.Identity.GetUserName(), model.NewPassword);
if (result.Success)
{
return RedirectToAction("Manage", new { Message = "Your password has been set." });
}
else
{
AddErrors(result);
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
How exactly would I go on about updating an ApplicationUser's Surname for example? Do I need to call the DbContext or?
I hope my question is clear.
Explore IdentityManager.Store.UserManagement and IdentityManager.Store.Users.
ApplicationUser cUser = (ApplicationUser) await IdentityManager.Store.Users.FindByNameAsync(HttpContext.User.Identity.Name, new System.Threading.CancellationToken());
cUser.Surname = "New Something";
IdentityResult result1 = await IdentityManager.Store.SaveChangesAsync();
Above code is an example only. Basically you need to explore the Store property of IdentityManage.
When we used the Users object of our database context we ran into other tracking errors. In our application, we would retrieve users as such
var user = UserManager.FindById(userId);
Edit the properties:
user.StorageName = "gooblygook";
//whatever other properties you would like to use
And then we would save it with the UserManager in the controller:
UserManager.Update(user);
This is currently a working solution for us.
Mark the Person object as virtual in your ApplicationUser definition. That worked for me.
public class ApplicationUser : IdentityUser
{
public virtual Person Person { get; set; }