I create update user in my App and then
I test my app in Postman and in Web App but create different result.
When I tried this code in postman it work but web app doesn't work
(Code in ASP.NET CORE 2.0, Web App using Angular 5)
[HttpPut("{id}")]
public async Task<IActionResult> UpdateUser(int id, [FromBody] UserForUpdateDto userDto) {
if(!ModelState.IsValid)
return BadRequest(ModelState);
var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
var userFromRepo = await _orgRepo.GetUser(id);
if(userFromRepo == null)
return NotFound($"User not found with id: {id}");
if (currentUserId != userFromRepo.Id)
return Unauthorized();
_mapper.Map<UserForUpdateDto, User>(userDto, userFromRepo);
if (await _orgRepo.SaveAll())
return NoContent();
throw new Exception($"Updating user {id} failed on save");
}
From the WebApp it produce error:
"Object reference not set to an instance of an object."
When I debug the app it seems the line caused that
var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
I check and it produce null.
Any idea where the User was set ?
My Login Controller:
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody]UserForLoginDto userForLoginDto)
{
var userFromRepo = await _repo.Login(userForLoginDto.Username.ToLower(), userForLoginDto.Password);
if (userFromRepo == null)
return Unauthorized();
// generate token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config.GetSection("AppSettings:Token").Value);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, userFromRepo.Id.ToString()),
new Claim(ClaimTypes.Name, userFromRepo.Username),
new Claim(ClaimTypes.Role, "RegisteredUsers")
}),
Expires = DateTime.Now.AddDays(3),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha512Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var user = _mapper.Map<UserForDetailDto>(userFromRepo);
return Ok(new { tokenString, user });
}
If an api method contains [Authorize] then an authorization header is sent along with the request. If no header is sent then you have no user.
[HttpPut("{id}")]
[Authorize(AuthenticationSchemes = "Bearer")]
public async Task<IActionResult> UpdateUser(int id, [FromBody] UserForUpdateDto userDto)
{
var sub = User.GetSubjectId(); // Subject Id is the user id
}
Related
I am using itfoxtec-identity-saml2 in my Dotnet 3.1 Project. I am initiating request from server and validating the login till here everything is working fine.
After getting response assertion from server and getting claims transformed and creating a session but still my application is unable to login.
Below are snippets of my code for reference.
AuthController.cs
[Route("AssertionConsumerService")]
public async Task<IActionResult> AssertionConsumerService()
{
try
{
var binding = new Saml2PostBinding();
var saml2AuthnResponse = new Saml2AuthnResponse(config);
binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
{
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
}
binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: (claimsPrincipal) => ClaimsTransform.TransformClaims(claimsPrincipal),isPersistent:true, lifetime: new TimeSpan(1, 0, 0));
var auth = HttpContext.User.Identity.IsAuthenticated;
}
catch (Exception ex)
{
}
return Redirect("~/");
}
ClaimsTransform.cs
public static ClaimsPrincipal TransformClaims(ClaimsPrincipal claimsPrincipal)
{
ClaimsIdentity identity = (ClaimsIdentity)claimsPrincipal.Identity;
var tenantId = identity.FindFirst(ClaimTypes.NameIdentifier);
var Name = identity.FindFirst("firstName");
var firstName = identity.FindFirst("firstName");
var Email = identity.FindFirst("Email");
var UserID = identity.FindFirst("UserID");
var claimsToKeep = new List<Claim> { tenantId, Name,firstName, Email, UserID };
var newIdentity = new ClaimsIdentity(claimsToKeep, identity.AuthenticationType, ClaimTypes.NameIdentifier, ClaimTypes.Role);
ClaimsPrincipal newClaims = new ClaimsPrincipal(newIdentity);
return new ClaimsPrincipal(new ClaimsIdentity(claimsToKeep, identity.AuthenticationType, ClaimTypes.Name, ClaimTypes.Role)
{
BootstrapContext = ((ClaimsIdentity)claimsPrincipal.Identity).BootstrapContext
});
//return newClaims;
}
After all this my application is redirecting back to login page instead of home page of the application with logged in user.
Help will be appreciated.
You need to set the users identity claim to a claim which exist in the claim set, otherwise the user is not accepted as being authenticated.
If eg. the tenantId claim is the users identity then the users identity claim is ClaimTypes.NameIdentifier in new ClaimsPrincipal(... ClaimTypes.NameIdentifier, ClaimTypes.Role)
ClaimsTransform.cs
public static ClaimsPrincipal TransformClaims(ClaimsPrincipal claimsPrincipal)
{
ClaimsIdentity identity = (ClaimsIdentity)claimsPrincipal.Identity;
var tenantId = identity.FindFirst(ClaimTypes.NameIdentifier);
var Name = identity.FindFirst("firstName");
var firstName = identity.FindFirst("firstName");
var Email = identity.FindFirst("Email");
var UserID = identity.FindFirst("UserID");
var claimsToKeep = new List<Claim> { tenantId, Name,firstName, Email, UserID };
return new ClaimsPrincipal(new ClaimsIdentity(claimsToKeep, identity.AuthenticationType, ClaimTypes.NameIdentifier, ClaimTypes.Role)
{
BootstrapContext = ((ClaimsIdentity)claimsPrincipal.Identity).BootstrapContext
});
}
I have a method to auth the user and create a token with expiration time, but if the token expired, the user cannot use the data. How to handle this?
This is my method:
[AllowAnonymous]
[HttpPost]
[Route("api/token")]
public IActionResult Post([FromBody]Personal personal)
{
string funcionID = "";
if (ModelState.IsValid)
{
var userId = GetUser(personal);
if (!userId.HasValue)
{
return Unauthorized();
}
else if (userId.Equals(2)) {
return StatusCode(404, "Vuelve a ingresar tu contraseƱa");
}
List<Claim> claims = new List<Claim>();
foreach (var funcion in Funcion) {
claims.Add(new Claim(ClaimTypes.Role, funcion.FuncionID.ToString()));
}
claims.Add(new Claim(JwtRegisteredClaimNames.Email, personal.CorreoE));
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
var sesionExpira = new DatabaseConfig();
_configuration.GetSection("Database").Bind(sesionExpira);
var token = new JwtSecurityToken
(
issuer: _configuration["Issuer"],
audience: _configuration["Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(sesionExpira.Sesion),
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])),
SecurityAlgorithms.HmacSha256)
);
var token_email = token.Claims.Where(w => w.Type == "email").Select(s => s.Value).FirstOrDefault();
var token_rol = claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role").Select(s => s.Value).FirstOrDefault();
var nombre = _context.Personal.Where(x => x.CorreoE == personal.CorreoE).Select(x => x.Nombre).FirstOrDefault();
return Ok(new { email = personal.CorreoE, token = new JwtSecurityTokenHandler().WriteToken(token), nombre = nombre, funcion = Funcion});
}
return BadRequest();
}
First, in the GetUser(Personal personal) method, that returns int, i return a number that i use to create a new token. Everything works fine, but i need some information to refresh the token if the time has expired
You can create middleware that will update the token. If you move your token creation logic to a separate service then you can do it like:
public class JwtTokenSlidingExpirationMiddleware
{
private readonly RequestDelegate next;
private readonly ITokenCreationService tokenCreationService;
public JwtTokenSlidingExpirationMiddleware(RequestDelegate next, ITokenCreationService tokenCreationService)
{
this.next = next;
this.tokenCreationService= tokenCreationService;
}
public Task Invoke(HttpContext context)
{
// Preflight check 1: did the request come with a token?
var authorization = context.Request.Headers["Authorization"].FirstOrDefault();
if (authorization == null || !authorization.ToLower().StartsWith("bearer") || string.IsNullOrWhiteSpace(authorization.Substring(6)))
{
// No token on the request
return next(context);
}
// Preflight check 2: did that token pass authentication?
var claimsPrincipal = context.Request.HttpContext.User;
if (claimsPrincipal == null || !claimsPrincipal.Identity.IsAuthenticated)
{
// Not an authorized request
return next(context);
}
// Extract the claims and put them into a new JWT
context.Response.Headers.Add("Set-Authorization", tokenCreationService.CreateToken(claimsPrincipal.Claims));
// Call the next delegate/middleware in the pipeline
return next(context);
}
}
And register it in Startup.cs:
public void Configure(IApplicationBuilder app)
{
...
app.UseMiddleware<JwtTokenSlidingExpirationMiddleware>();
...
}
I did something similiar to an old application using the method RefreshTokenAsync from IdentityModel.
You could try something like this when the User gets an unauthorized:
var identityService = await DiscoveryClient.GetAsync("http://localhost:5000");
// request token
var tokenClient = new TokenClient(identityService.TokenEndpoint, "client", "secret");
var tokenResponse = await tokenClient.RequestRefreshTokenAsync(refreshToken);
return Ok(new { success = true, tokenResponse = tokenResponse });
Source: https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/issues/4
EDIT: I have edited my original answer to provide a more clear and better answer according to the rules.
I have created a .NET Core 2.0 Restful API, which has login functionality enabled with openiddict and JWS tokens. I also have a .NET Core Web App that consumes the API. Currently I am using HttpContext.Session on the client side to store and get the tokens in order to login the users. Since I am fairly new to .NET Core I wanted to ask if this is a good way to implement login functionality and if not how should I do it?
WebAPI AuthorizationController
[HttpPost("~/connect/token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest request)
{
Debug.Assert(request.IsTokenRequest(),
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
var userRole = await _userManager.GetRolesAsync(user);
var loginRole = request.GetParameters().ElementAt(3).Value;
if (user == null || userRole.ElementAt(0) != loginRole)
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
// Validate the username/password parameters and ensure the account is not locked out.
var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true);
if (!result.Succeeded)
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
// Create a new authentication ticket.
var ticket = await CreateTicketAsync(request, user);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
WebApp AccountController
[HttpPost]
public IActionResult Login(LoginViewModel vm)
{
if (ModelState.IsValid)
{
using (HttpClient httpClient = new HttpClient())
{
Dictionary<string, string> tokenDetails = null;
var fullUrl = _appSettings.Value.Apis.GSRTCApi.Url + _appSettings.Value.Apis.GSRTCApi.LoginEndpoint;
HttpClient client = new HttpClient
{
BaseAddress = new Uri(fullUrl)
};
var login = new Dictionary<string, string>
{
{"grant_type", "password"},
{"username", vm.Email},
{"password", vm.Password},
{"role", vm.Role}
};
var response = client.PostAsync("Token", new FormUrlEncodedContent(login)).Result;
if (response.IsSuccessStatusCode)
{
tokenDetails = JsonConvert.DeserializeObject<Dictionary<string, string>>(response.Content.ReadAsStringAsync().Result);
if (tokenDetails != null && tokenDetails.Any())
{
var tokenNo = tokenDetails.ElementAt(2).Value;
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + tokenNo);
HttpContext.Session.SetString("Username", vm.Email);
HttpContext.Session.SetString("Token", tokenNo);
//return RedirectToAction("Index", "Dashboard");
return Json(Url.Action("Index", "Dashboard"));
}
}
}
}
return PartialView("../Account/_Login", vm);
}
In our project we have a user management system build on the ASP.NET Identity framework.
When a user registers by providing an email, username and password, everything works fine. We are able to get the users ID in the method in every controller that inherit "ApiController".
However, now we are trying to implement external log in providers, and we are starting off with Facebook. The registration is going smooth, and the user is created in our database, just any other user, but without a PasswordHash of cause, and an access token is retured back to the client for further authorization.
All of that is working as it should, but when it comes to the part, where the programmer should be able to receive the users id with "User.Identity.GetUserId", we are having a little problem. The "User.Identity" is containing the right "userName" but the "GetUserId" is always returning "null".
The following is our registration method, and the generation of the access token
[OverrideAuthentication]
[AllowAnonymous]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var verifiedAccessToken = await VerifyExternalAccessToken(model.Provider, model.AccessToken);
if (verifiedAccessToken == null)
{
return BadRequest("Invalid Provider or External Access Token");
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
var info = new ExternalLoginInfo()
{
DefaultUserName = model.UserName,
Login = new UserLoginInfo(model.Provider, verifiedAccessToken.user_id)
};
var accessTokenResponse = GenerateLocalAccessTokenResponse(model.UserName, user.Id, model.Provider);
return Ok(accessTokenResponse);
}
catch (Exception e)
{
return null;
}
}
private JObject GenerateLocalAccessTokenResponse(string userName, string userid, string provider)
{
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Sid, userid));
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),
new JProperty("token_type", "bearer"),
new JProperty("external_provider", provider),
new JProperty("expires_in", tokenExpiration.TotalSeconds),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
return tokenResponse;
}
So all in all, every part of the registration is working as it should, we are just not able to receive the user id of the current user, when it uses an access token for a user created by an external provider.
Well, in case someone in the future needs the answer, it was because the "GetUserId" looked for the claim called "NameIdentifier", so changeing it, made it work.
I'm having troubles .NET Core Web API app authentication.
I want to:
1) Authenticate user with Google on Mobile App (currently iOS)
2) Using this authentication, create user record in database using AspNetCore.Identity and Entity Framework Core
3) Using same authentication, call Google Calendar API from .NET Core server
So far I figured out how to implement 1 and 3, but can't wrap my head around number 2.
My understanding is that to sign in user authenticated with third-party, due to documentation, you need to use SignInManager instance method ExternalLoginSignInAsync. It takes two arguments:
login provider (should be simple stirng as "Google") and unique provider key. My problem is that I can't find anywhere where can I get one.
Here is the list of all things I receive from Google Sign In result on mobile app:
Here is the method I try to call in.
// POST api/signup
[HttpPost]
public async Task<bool> Post([FromBody]string authorizationCode, [FromBody]string userId)
{
var tokenFromAuthorizationCode = await GetGoogleTokens(userId, authorizationCode);
var result = await signInManager.ExternalLoginSignInAsync(
"Google", tokenFromAuthorizationCode.IdToken, false);
if (result.Succeeded)
return true;
var externalLoginInfo = new ExternalLoginInfo(
ClaimsPrincipal.Current, "Google", tokenFromAuthorizationCode.IdToken, null);
return await SignInUser(externalLoginInfo);
}
private async Task<bool> SignInUser(ExternalLoginInfo info)
{
var newUser = new AppUser { Email = "test#test.com", UserName = "TestUser" };
var identResult = await userManager.CreateAsync(newUser);
if (identResult.Succeeded)
{
identResult = await userManager.AddLoginAsync(newUser, info);
if (identResult.Succeeded)
{
await signInManager.SignInAsync(newUser, false);
return true;
}
}
return false;
}
private async Task<TokenResponse> GetGoogleTokens(string userId, string authorizationCode)
{
TokenResponse token;
try
{
// TODO: Save access and refresh token to AppUser object
token = await authFlow.Flow.ExchangeCodeForTokenAsync(
userId, authorizationCode, "http://localhost:60473/signin-google", CancellationToken.None);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
return token;
}
My question is: is it a correct path if you're building authentication via REST API, and if so, where could I get Google's provider key?
Thanks in advance.
Well apparently, provider key is just user id from Google.
Here is the solution that worked for me:
[HttpPost]
public async Task<AppUser> Post([FromBody]GoogleSignInCredentials credentials)
{
// 1. get user id from idToken
var oauthService = new Oauth2Service(new BaseClientService.Initializer { ApiKey = "{your api key}" });
var tokenInfoRequest = oauthService.Tokeninfo();
tokenInfoRequest.IdToken = credentials.IdToken;
var userInfo = await tokenInfoRequest.ExecuteAsync();
// 2. get access_token and refresh_token with new id and authorization code
var tokenFromAuthorizationCode = await GetGoogleTokens(userInfo.UserId, credentials.AuthorizationCode);
// 3. check if user exists
var result = await _signInManager.ExternalLoginSignInAsync(
"Google", userInfo.UserId, false);
if (result.Succeeded)
return await _userManager.FindByEmailAsync(userInfo.Email);
// 4. create user account
var externalLoginInfo = new ExternalLoginInfo(
ClaimsPrincipal.Current, "Google", userInfo.UserId, null);
// 5. fetch user
var createdUser = await SignInUser(externalLoginInfo, userInfo.Email);
if (createdUser != null)
{
createdUser.GoogleAccessToken = tokenFromAuthorizationCode.AccessToken;
createdUser.GoogleRefreshToken = tokenFromAuthorizationCode.RefreshToken;
var updateResult = await _userManager.UpdateAsync(createdUser);
if (updateResult.Succeeded)
return createdUser;
return null;
}
return null;
}
private async Task<AppUser> SignInUser(ExternalLoginInfo info, string email)
{
var newUser = new AppUser { Email = email, UserName = email };
var identResult = await _userManager.CreateAsync(newUser);
if (identResult.Succeeded)
{
identResult = await _userManager.AddLoginAsync(newUser, info);
if (identResult.Succeeded)
{
await _signInManager.SignInAsync(newUser, false);
return await _userManager.FindByEmailAsync(email);
}
}
return null;
}
private async Task<TokenResponse> GetGoogleTokens(string userId, string authorizationCode)
{
return await _authFlow.Flow.ExchangeCodeForTokenAsync(
userId, authorizationCode, "http://localhost:60473/signin-google", CancellationToken.None);
}