Web API 2 AccessFailedCount not Incrementing When using Token Based Authentication - asp.net

I am using Webapi with Identity2.0 AccessFailedCount, LockoutEndDateUtc is not incermenting on Invalid UserName and Password. I have implement Token Based Authentication provided by WebAPI. Please help .
here is code Snippet
using (UserManager<ApplicationUser> userManager = userManagerFactory)
{
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
if (await userManager.IsLockedOutAsync(user.Id))
{
context.SetError("lock_out", "The account is locked.");
return;
}
if (!userManager.IsEmailConfirmed(user.Id))
{
context.SetError("inactive_user", "The user is not active. Please check your Register Email to verify.");
return;
}
ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
context.Options.AuthenticationType);
ClaimsIdentity cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}

Finally I have resolved with this code
// To lock the user with userName ---- setting of maximum access 5 in IdentityConfig.cs File
ApplicationUser userToLock = await userManager.FindByNameAsync(context.UserName);
if (userToLock != null)
{
await userManager.AccessFailedAsync(userToLock.Id);
}
Now Access AccessFailedCount, LockoutEndDateUtc getting value
Thanks for the help guys. Special Thanks for #trailmax ... To divert my thinking to webapi

To increment AccessFailedCount on a user, every time the login is invalid you need to call for
await userManager.AccessFailedAsync(user.Id);
Otherwise this is not done for you in any way.
ApplicationSignInManager does this this for you but (as far as I know) this class only works with MVC, not WebAPI

Hi It may be too late but I got some code from ASP.Net Identity 2.0 AccessFailedCount not incrementing
and customized to Web API.
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindByNameAsync(context.UserName);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
bool EmailConfirmed = await userManager.IsEmailConfirmedAsync(user.Id);
if ( !EmailConfirmed)
{
context.SetError("inactive_user", "The user is not active. Please check your Register Email to verify.");
return;
}
bool LockedOut = await userManager.IsLockedOutAsync(user.Id);
if (userManager.SupportsUserLockout && LockedOut)
{
context.SetError("invalid_grant", "This account has been locked out, please try again later.");
return;
}
int FailedCount = await userManager.GetAccessFailedCountAsync(user.Id);
bool LockoutEnabled = await userManager.GetLockoutEnabledAsync(user.Id);
if (userManager.CheckPassword(user, context.Password))
{
if (userManager.SupportsUserLockout && LockoutEnabled && FailedCount > 0)
{
await userManager.ResetAccessFailedCountAsync(user.Id);
}
// Authenticate user
}
else
{
if (userManager.SupportsUserLockout && LockoutEnabled)
{
await userManager.AccessFailedAsync(user.Id);
}
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}

Related

User login using Email or Username gives null if user not found in ASP.NET Core MVC

I am using the default identity pages with some modifications, in the login page I included the username for the user to login. It works perfectly, the user now can login by both the email and username, but when the users enters false info, a null exception appears instead of showing
Invalid login attempt
Code:
if (ModelState.IsValid)
{
//Check if user entered email or username in the Input.Email property
var user = await _userManager.FindByNameAsync(Input.Email) ?? await _userManager.FindByEmailAsync(Input.Email);
// user is null if not exist? error
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(user, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
//some code
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
I get why the error happens, but don't know what the proper way to solve it.
Originally the signInManager checks the user input not the actual user, so if the input is not found it will not be succeeded, how can I do it the same old way?
when the users enters false info, a null exception appears
In source code of PasswordSignInAsync(TUser, String, Boolean, Boolean) method, we can find it will throw NullException error if user is null.
public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string password,
bool isPersistent, bool lockoutOnFailure)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
return attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
}
how can I do it the same old way?
You can modify the code as below to check if user is null, and set and display "Invalid login attempt." error message.
var user = await _userManager.FindByNameAsync(Input.Email) ?? await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
var result = await _signInManager.PasswordSignInAsync(user, Input.Password, Input.RememberMe, lockoutOnFailure: true);
//...
//code logic here

How to store cookies in asp.net identity (web api)

I have Asp.net web Application project with WebApi and individuals user Account.
I implemented Registration and login, and I used angularjs in front end.
Now, I need to store cookies in browser.
I'm confuse if authentication based on cookies store cookies in browser?
In my project I authenticate users based on token.
I do research but it confuse me and I did't find a clear guide to store cookies using Asp.Net Identity webApi.
Here is where is user authenticate:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
/*
I tried the following but I get an error that Request doesn't exist
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, context.UserName));
var id = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
*/
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
I'm sorry, I'm new in Asp.net Identity.
If there is a clear guide to do this in Asp.Net Identity in WebApi (Not MVC)?
Note: I don't have LoginPath.
So you have a webApi that you use to authenticate your users and when your user is authenticated you get a jwt token, Right ? and you need to save that token in a cookie so that your front-end would use to make requests.
so here is a sample code i wrote to do just that is asp core api
in my aspcore API AccountsController => Login Action:
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginViewModel vm)
{
if (ModelState.IsValid)
{
var token = await _authenticationService.GenerateToken(vm);
if (token != null)
{
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
return StatusCode(StatusCodes.Status403Forbidden, new { ErrorMessage = "wrong Email or password" });
}
the _authenticationService is a class that i wrote to generatae jwt token :
public async Task<JwtSecurityToken> GenerateToken(LoginViewModel vm)
{
if (!string.IsNullOrEmpty(vm.Email) && !string.IsNullOrEmpty(vm.Password))
{
var user = await _userManager.FindByEmailAsync(vm.Email);
var userRoles = await _userManager.GetRolesAsync(user);
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Secrets:SigningKey").Value));
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, vm.Email)
};
foreach (var roleName in userRoles)
{
claims.Add(new Claim(ClaimsIdentity.DefaultRoleClaimType, roleName));
};
if (user != null && await _userManager.CheckPasswordAsync(user, vm.Password))
{
var token = new JwtSecurityToken(
issuer: _configuration.GetSection("Secrets:issuer").Value,
audience: _configuration.GetSection("Secrets:audience").Value,
expires: DateTime.UtcNow.AddDays(20),
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
claims: claims);
return token;
}
return null;
}
return null;
}
so in the code above you if you send a good credentials to your api you will get a jwt token back.
now you need to save that token in a cookie, to do that you need to create the cookie in your front-end not your web api.
so you might see this link in angular-university

Authorization in Asp.net Core 2.0 with maria database?

When a User logins into a Web application. The Website has to authenticate who the User against the Database. Once the User is authenticated then a system can authorise the person so that they can make request to other pages without logging in again. I am having a implementing security into my asp.net core 2.0 web application, because I am not using Entity Framework. I am using MariaDB as the database Back-End. When a request is made to the Server
public ViewResult testMethod(LoginModel model){
User user = dataManager.GetUser(model.Email);
if (user == null)
{
// return user to view
return View(model);
}
if (user.Activated != true)
{
// return user to view
return View(model);
}
// Use userManager
I want to use the UserManager but since I am using a MariaDB as the backend is this still possible?
if you are using, entity framework
let say you have table User with password
try using claim principal
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
try
{
var user = _authService.Login(model.UserName, model.Password);
if (user != null)
{
var userClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Name),
new Claim(ClaimTypes.NameIdentifier, user.UserId),
new Claim(ClaimTypes.Email, user.Mail),
new Claim(ClaimTypes.Role, user.PositionId)
};
var identity = new ClaimsIdentity(userClaims, "Ix");
//userClaims.Add(new Claim(ClaimTypes.Role, "0"));
var principal = new ClaimsPrincipal(identity);
await HttpContext.Authentication.SignInAsync("app", principal, new AuthenticationProperties { IsPersistent = false });
return RedirectToLocal(returnUrl);
}
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
}
}
return View(model);
}

Invalid token in ConfirmEmail due to changed Securitystamp

I've been banging my head against a wall for some time now about this:
I have an ASP.NET MVC 5.2.3 web application with ASP.NET Identity 2.2.1. I want to force users to
validate their email-address and
validate their mobile phone number.
So when a user registers for the application an emailVerification token is generated and sent to the user.
After that the user is redirected to the VerifyPhoneNumber endpoint in the Manage controller. SMS-code is generated and gets send to the user. User is promted to enter the SMS-code. Code is verified.
BUT if then the user receives the email with the email-verification-code and click the link the token cannot no longer be verified (Invalid Token).
As far as I understand, this happens because calling UserManager.ChangePhoneNumberAsync changes the user's SecurityStamp. Email-verification works well if phone verification is not active. To be more specific, when ChangePhoneNumberAsync is not called.
Any ideas on how to prevent the SecurityStamp from changing or allow both verifications on inital registration are greatly appreciated.
Ben
VerifyPhoneNumber
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var userId = User.Identity.GetUserId();
var result = await UserManager.ChangePhoneNumberAsync(userId, model.PhoneNumber, model.Code);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToAction("Index", "Home");
}
else
{
return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "Could not verify phone number.");
return View(model);
}
ConfirmEmail
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
code = HttpUtility.UrlDecode(code);
var result = await UserManager.ConfirmEmailAsync(userId, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}

MVC 5 Web API with Facebook access token to RegisterExternal without need of Cookie

Setup:
New MVC5 Project with just Web API. Added Facebook AppId and Secret.
I can get Token for my Web API from Token endpoint by passing in UserName and Password. Then use that token for further calls.
BUT
I want to register new users with the help of Facebook SDK in iOS app.
I am using Facebook SDK to get Access Token. (Assume at this point, I have an Access Token).
Next thing I know is to call api/Account/RegisterExternal endpoint by passing this token in Authorization header with Bearer [Access Token] but this result in 500 server error.
I guess I know the reason, Cookie is missing. I made the same call with a cookie from Fidler and it worked. (Cookie is received by going to URL provided by ExternalLogins endpoint).
As cookie is missing await Authentication.GetExternalLoginInfoAsync(); inside the RegisterExternal action returns null.
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null)
{
return InternalServerError();
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
I don't want to make 3 calls to my Web API to ask for external logins and then goto that URL and authenticate in a Web Browser for Facebook access token and then call the RegisterExternal endpoint with that access token and Cookie that I need to collect between these calls.
As I said I didn't change anything in template except the Facebook Ids. Still the code is as below.
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
app.UseFacebookAuthentication(
appId: "xxxxxxxxxxxxxxx",
appSecret: "xxxxxxxxxxxxxxxxxxxxxxxx");
}
}
as far as I know, Web API doesn't need Cookie and that appears true when I have Local Token from Token endpoint but why does it require Cookie in the first place when doing ExternalRegister
WebApiConfig class looks like this and shouldn't config.SuppressDefaultHostAuthentication(); avoid any Cookie needs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
I don't know if I am missing the point here.. My intentions are to not need to use web browser in a native iOS app for the token. That is Facebook SDK to get access token and using that call RegisterExternal to get the Local Token and create that users Identity.
I did my homework and I am stuck on this thought.
Thoughts appreciated!
I was mistaken that it accepts the Social Token with cookie!
It doesn't accept any External Token directly.
The thing is.. MVC 5 is taking care of everything for us, i.e. collecting token from Social Medias and validating/processing it. After that it generates a local token.
The RegisterExternal method also requires cookies to be maintained, the solution does not.
I have written a blog post which will explain in detail. Added the straight forward answer below. I aimed to make it blend and feel integral part of Login/Signup flow of default MVC Web API to make sure its easy to understand.
After the below solution, Authorize attribute must be as below to work or you will get Unauthorized response.
[Authorize]
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalBearer)]
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie)]
Use ExternalBearer if you want to allow only Tokens to use API, use ApplicationCookie if you want to allow only Logged cookie to use API i.e. from a website. User both if you want to allow the API for both.
Add this action to AccountController.cs
// POST api/Account/RegisterExternalToken
[OverrideAuthentication]
[AllowAnonymous]
[Route("RegisterExternalToken")]
public async Task<IHttpActionResult> RegisterExternalToken(RegisterExternalTokenBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ExternalLoginData externalLogin = await ExternalLoginData.FromToken(model.Provider, model.Token);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != model.Provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return InternalServerError();
}
ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
ClaimsIdentity identity = null;
IdentityResult result;
if (hasRegistered)
{
identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
IEnumerable<Claim> claims = externalLogin.GetClaims();
identity.AddClaims(claims);
Authentication.SignIn(identity);
}
else
{
user = new ApplicationUser() { Id = Guid.NewGuid().ToString(), UserName = model.Email, Email = model.Email };
result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
var info = new ExternalLoginInfo()
{
DefaultUserName = model.Email,
Login = new UserLoginInfo(model.Provider, externalLogin.ProviderKey)
};
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
IEnumerable<Claim> claims = externalLogin.GetClaims();
identity.AddClaims(claims);
Authentication.SignIn(identity);
}
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromDays(365));
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
// Create the response building a JSON object that mimics exactly the one issued by the default /Token endpoint
JObject token = new JObject(
new JProperty("userName", user.UserName),
new JProperty("id", user.Id),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", TimeSpan.FromDays(365).TotalSeconds.ToString()),
new JProperty(".issued", currentUtc.ToString("ddd, dd MMM yyyy HH':'mm':'ss 'GMT'")),
new JProperty(".expires", currentUtc.Add(TimeSpan.FromDays(365)).ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"))
);
return Ok(token);
}
Add this helper method to ExternalLoginData class in helper region in AccountController.cs
public static async Task<ExternalLoginData> FromToken(string provider, string accessToken)
{
string verifyTokenEndPoint = "", verifyAppEndpoint = "";
if (provider == "Facebook")
{
verifyTokenEndPoint = string.Format("https://graph.facebook.com/me?access_token={0}", accessToken);
verifyAppEndpoint = string.Format("https://graph.facebook.com/app?access_token={0}", accessToken);
}
else if (provider == "Google")
{
return null; // not implemented yet
//verifyTokenEndPoint = string.Format("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={0}", accessToken);
}
else
{
return null;
}
HttpClient client = new HttpClient();
Uri uri = new Uri(verifyTokenEndPoint);
HttpResponseMessage response = await client.GetAsync(uri);
ClaimsIdentity identity = null;
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
dynamic iObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
uri = new Uri(verifyAppEndpoint);
response = await client.GetAsync(uri);
content = await response.Content.ReadAsStringAsync();
dynamic appObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
if (provider == "Facebook")
{
if (appObj["id"] != Startup.facebookAuthOptions.AppId)
{
return null;
}
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, iObj["id"].ToString(), ClaimValueTypes.String, "Facebook", "Facebook"));
}
else if (provider == "Google")
{
//not implemented yet
}
}
if (identity == null)
return null;
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer) || String.IsNullOrEmpty(providerKeyClaim.Value))
return null;
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
return null;
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name)
};
}
and finally, the RegisterExternalTokenBindingModel being used by the action.
public class RegisterExternalTokenBindingModel
{
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[Display(Name = "Token")]
public string Token { get; set; }
[Required]
[Display(Name = "Provider")]
public string Provider { get; set; }
}
Yes, we pass the email along with Token details while registering, this will not cause you to change the code when using Twitter, as Twitter doesn't provide users email. We verify token comes from our app. Once email registered, hacked or somebody else's token cannot be used to change email or get a local token for that email as it will always return the local token for the actual user of the Social Token passed regardless of the email sent.
RegisterExternalToken endpoint works to get token in both ways i.e. register the user and send the Local token or if the user already registered then send the token.
Before everything, this is NOT A FULL Answer, this is just a note or an addition for the answer to avoid some problems which could cost you handful of days (in my case 3 days)
The previous answer is the full answer it just lacks from one thing, which is the following:
if you specified a role for the Authorize attribute, for example [Authorize("UserRole")] , the previous setup will still give you 401 error because the solution does not set the RoleClaim
and to solve this problem you have to add this line of code to the RegisterExternalToken method
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, "UserRole"));

Resources