How to get UserID from ASP.NET Identity when sending Rest Request - c#

I have an API which uses ASP.NET Identity, it is fairly easy for me to get the UserId once the token has been generated as following
HttpContext.Current.User.Identity.GetUserId().ToString())
Now I have an external application which is trying to authenticate using this API but I need the UserId of the user who generated the token
When I send a request to http://myapiURL/token I get the following
access_token
token_type
expires_in
userName
issued
expires
And when I send a request to get API/Account/UserInfo using the generated token I get the following
Email
HasRegistered
LoginProvider
Question How do I get UserId?
I have two options,
A. I modify UserInfoViewModel GetUserInfo() to have UserId in UserInfoViewModel?
B. I create a new method in ApiController such as GetUserId (API/Account/GetUserId) which runs HttpContext.Current.User.Identity.GetUserId().ToString()) and sends back the
UserId
Is there any other way?
Cheers

I believe you want UserId in the response of /Token.
By default Identity does not add UserId in response.
so you need to add it manually in ApplicationOAuthProvider.cs in method GrantResourceOwnerCredentials
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;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
ticket.Properties.Dictionary.Add("UserId", user.Id);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}

Related

Add IdentityUser to HttpContext.User

I am using .NET Core 3.0 . I am trying to authenticate a user via an API Key authorization. I have created a middleware that is called every time a controller endpoint is called. I can authenticate the user and get the claims but I cannot set the HttpContext.User object to use this identity.
public async System.Threading.Tasks.Task Invoke(HttpContext context, Services.AuthenticationService authenticationService, UserManager<IdentityUser> userManager, SignInManager<IdentityUser> _signInManager)
{
if (!context.Request.Path.ToString().Contains("Token") && context.Request.Path.StartsWithSegments(new PathString("/api")))
{
var userKey = context.Request.Headers["user-key"];
// Register the user and its claims to the session
var user = authenticationService.GetUserFromToken(userKey);
var claims = await userManager.GetClaimsAsync(user);
if (claims == null)
{
context.Response.StatusCode = 401; //UnAuthorized
await context.Response.WriteAsync("Claims not found for user");
return;
}
var claimsIdentity = new ClaimsIdentity(IdentityConstants.ApplicationScheme);
claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
claimsIdentity.AddClaims(claims);
context.User.AddIdentity(claimsIdentity);
await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme, new ClaimsPrincipal(claimsIdentity));
}
await _next.Invoke(context);
}
When I check HttpContext.User in my controller the main identity object has no information. In the Identities object there are two identies. The second identity has the the claim and user name that I set in the middleware.
How do I set the main identity to be the one and only identity? The _signInManager code doesn't actually do anything and I get the same results in the HttpContext.User object with or without it.

Update Identity User Claims in Web API

I'm currently trying to update the user's email/username from a mobile app to a Web API project. I'm currently using oauth and token authentication. When updating the identity user, the user becomes unauthenticated because the username and access token are no longer valid. From what I have read, I must update the identity claims. This is what I have tried so far:
var identity = new ClaimsIdentity(User.Identity);
if (result)
{
var identityUser = await UserManager.FindByNameAsync(User.Identity.Name);
identityUser.Email = AntiXssEncoder.HtmlEncode(value.Email, true);
identityUser.UserName = AntiXssEncoder.HtmlEncode(value.Email, true);
var identityResult = await UserManager.UpdateAsync(identityUser);
if(identityResult.Succeeded)
{
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
await UserManager.RemoveClaimAsync(identityUser.Id, identity.FindFirst(ClaimTypes.Name));
await UserManager.AddClaimAsync(identityUser.Id, new Claim(ClaimTypes.Name, value.Email));
identity.RemoveClaim(identity.FindFirst(ClaimTypes.Name));
identity.AddClaim(new Claim(ClaimTypes.Name, value.Email));
authenticationManager.AuthenticationResponseGrant =
new AuthenticationResponseGrant(
new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = false });
}
return Ok();
}
However, it still shows the previous email when using User.Identity.Name and the claims for the user within the authenticationManager have not been updated either. I'm not sure what else to do as there isn't much documentation on this for Web API. Any help is greatly appreciated.
Main problem is that claim which represents user's name is not updated in ClaimsIdentity you are using in the last step.
The easiest way to perform the update is to use SignInManager<TUser, TKey>.SignIn method
signInManager.SignIn(identityUser, isPersistent: false, rememberBrowser: false);
This is also an ASP.NET Identity idiomatic way since it is using associated IClaimsIdentityFactory to create claims for new identities.
Complete example
static async Task<IdentityResult> UpdateEmailAsync<TUser>(
IPrincipal principal,
UserManager<TUser, string> userManager,
SignInManager<TUser, string> signInManager,
string newEmail
)
where TUser : class, IUser<string>
{
string userId = principal.Identity.GetUserId();
IdentityResult result = await userManager.SetEmailAsync(userId, newEmail);
if (result.Succeeded)
{
// automatically confirm user's email
string confirmationToken = await userManager.GenerateEmailConfirmationTokenAsync(userId);
result = await userManager.ConfirmEmailAsync(userId, confirmationToken);
if (result.Succeeded)
{
TUser user = await userManager.FindByIdAsync(userId);
if (user != null)
{
// update username
user.UserName = newEmail;
await userManager.UpdateAsync(user);
// creates new identity with updated user's name
await signInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
// succeded
return result;
}
}
// failed
return result;
}
Then you can just call it from your code
string newEmail = AntiXssEncoder.HtmlEncode(value.Email, true);
IdentityResult result = await UpdateEmailAsync(identityUser, UserManager, SignInManager, newEmail);
if (result.Succeeded)
{
return Ok();
}

ASP.NET web api "Remember Me" functionality using cookies

I am trying to implement a "Remember Me" functionality in my Web Api project.
I would like to :
have the Remember Me functionality when the user Sign In.
save a cookies for to keep the user always logged in, so that the user no need type the username and password every single time when they visit the websites.
Sign the user in by reading the cookies that saved on the last login.
One more question that I am thinking about is... I am trying to generate the cookies by using JavaScript when the user checked the Remember Me Checkbox. Is it possible to do this?
OR
I should implement the RememberMe() in the AccountController??
Addition:
Here's my code in ApplicationOAuthProvider.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindByNameAsync(context.UserName);
if (user == null) {...}
if (userManager.IsLockedOut(user.Id)) {...}
if (!(await userManager.CheckPasswordAsync(user, context.Password)))
{ ... }
if (!user.EmailConfirmed) {...}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
In my JavaScript.
$('#checkbox').click(function () {
if ($('#checkbox').is(':checked')) {
// save username and password
username = $('#txtLoginEmail').val();
password = $('#pass').val();
checkbox = $('#chkRememberMe').val();
} else {
username = '';
password = '';
checkbox = '';
}
});
You need to implement refresh tokens in you app to be able to offer this functionality.
Basically, you need to create a RefreshTokenOAuthProvider that will generate refresh tokens. You can use 2 types of client_id to make a difference between clients who need to be remembered or not.
It is explained in this excellent series of blog posts (though it might start to become a little bit outdated, the information regarding owin setup is gold).

Identity Framework User Lockdown

I'm trying to lock user login after 3 unsuccessful login attempts for 5 minutes. I have add this 3 lines to App_Start/IdentityConfig.cs public static ApplicationUserManager Create( ... ) method:
manager.MaxFailedAccessAttemptsBeforeLockout = 3;
manager.DefaultAccountLockoutTimeSpan = new TimeSpan(0, 5, 0);
manager.UserLockoutEnabledByDefault = true;
After that I register new user via POST /api/Account/Register (in default scaffolded AccountController). Account is created and LockoutEnabled property is set to true. But if I try to login for via POST /Token few times with wrong password account isn't locked down.
I'm also interested where is implementation of /Token endpoint. Is it in AccountController GET api/Account/ExternalLogin. I have set breakpoint there but execution wasn't stopped there when I tried to login.
What am I missing?
If you are using the default Web API template from Visual Studio, you have to change the behavior of GrantResourceOwnerCredentials method of the ApplicationOAuthProvider class (found inside the Provider folder of your Web API project). Something like this could allow you to track failed login attempts, and stop locked out users from logging in:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await userManager.FindByNameAsync(context.UserName);
if (user == null)
{
context.SetError("invalid_grant", "Wrong username or password."); //user not found
return;
}
if (await userManager.IsLockedOutAsync(user.Id))
{
context.SetError("locked_out", "User is locked out");
return;
}
var check = await userManager.CheckPasswordAsync(user, context.Password);
if (!check)
{
await userManager.AccessFailedAsync(user.Id);
context.SetError("invalid_grant", "Wrong username or password."); //wrong password
return;
}
await userManager.ResetAccessFailedCountAsync(user.Id);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
Be aware that this way you can only lock out users trying to login using the password grant (Resource Owner Credentials). If you also want to disallow locked out user to login using other grants, you have to override the other methods (GrantAuthorizationCode, GrantRefreshToken, etc.), checking if await userManager.IsLockedOutAsync(user.Id) is true in those methods too.

How to customize oAuth token providers in asp.net web api

I want to change the way the default bearer token system works.
I want to login to the webAPI providing the username, password, and mac address of the device. Like so.
Content-Type: application/x-www-form-urlencoded
username=test&password=P#ssword&grant_type=password&client_id=android&device_info=MAC_Address
I then want the API to provide me with a Refresh Token. This token will be valid for say 7 days and will allow for me to get a new access token. However in the refresh token I want to save / embed the security stamp of the users password in the token along with the extirpation date. This way I can check the security stamp when a new access token is requested. (solves password changing scenario)
My access token only needs to store the bare amount of information for it to work. I don't require that the access token store anything specific. I would like to keep it as small as possible. When it expires I will simply request a new access token using my refresh token.
Now I have tried to implement the above but have got my self heavily confused about what to implement where. Here's what i have got.
Step 1: The Startup.Auth.cs
//Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
Provider = new SimpleAuthorizationServerProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20)
};
Now in here I already have some questions. I want to have two providers, one which handles Refresh Tokens and one that handles Access Tokens. Which providers do I need to set? because I see there is also one called AccessTokenProvider = then what is Provider = for?
Step 2: The RereshTokenProvider. This is what I have so far:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
//Used to store all the refresh tokens
public static ConcurrentDictionary<string, AuthenticationTicket> RefreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString("N");
//copy properties and set the desired lifetime of refresh token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
};
//TODO: get mac address from the request headers??
//TODO: save the mac address to db along with user and date
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
RefreshTokens.TryAdd(guid, refreshTokenTicket);
context.SetToken(guid);
return Task.FromResult<object>(null);
}
public Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (RefreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
return Task.FromResult<object>(null);
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
}
Now if i understand correctly. The purpose of the SimpleRefreshTokenProvider is to build up a RefreshToken and to the validate it when the api receives a request with one in it?
Step 3: SimpleAuthorizationServerProvider. This is what I have so far. but I have a feeling this is where I have gone wrong. Or im getting confused, What is the purpose of this class? Is it not to validate the AccessToken?
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Not concerned about clients yet
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// validate user credentials
var userManager = context.OwinContext.GetUserManager<FskUserManager>();
FskUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
// create identity
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
//Set properties of the token
// create metadata to pass on to refresh token provider
AuthenticationProperties properties = new AuthenticationProperties(new Dictionary<string, string>
{
{"userName", user.UserName}
});
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
string originalClient;
context.Ticket.Properties.Dictionary.TryGetValue("as:client_id", out originalClient);
var currentClient = context.ClientId;
// chance to change authentication ticket for refresh token requests
var newId = new ClaimsIdentity(context.Ticket.Identity);
newId.AddClaim(new Claim("newClaim", "refreshToken"));
var newTicket = new AuthenticationTicket(newId, context.Ticket.Properties);
context.Validated(newTicket);
}
}
Please what am I missing here?

Categories