How to get access token in Web Api OAuth? - c#

I have a Web Application that generates the link to get an access token against a web API 2.
Basically, the following controller action is called:
GetExternalLogin at AccountController:
ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
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);
}
else
{
// as user is not registered, this block is hit
IEnumerable<Claim> claims = externalLogin.GetClaims();
ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
Authentication.SignIn(identity);
}
return Ok();
Now, this return Ok line simply returns to my Web API base url and add a token after this:
https://localhost:44301/#access_token=iPl1MSgnjI3oXgDxuCH9_t5I1SsELUH-v_vNXdehGpNWsCWsQaX7csWWadWRq4H2uZ0BB8zZm2s0xOI8TSOfgzH7QbFVko4Ui8jM5SylhPgkC7eiQG-kChDfa5HMlxKF1JvRg9Kvs40rPGqsC22uel-Gi2QZlrMh_5M0NT06QOOMv4bDTAFljKw9clsMiHidX4TPfQ6UmhROMIo8FcBDlAfH7wZbSQZjFAWm4Mub-oMoUxUOzAVxJrjGiM9gxwk4iqLqGbcFVl6AncJnFO_YDtmWH_sRBvmbfzpQ6GiB10eyY-hA_L-sWtQbX8IPPtOKuWGbyg0_MfaWBfAJfUiNjH6_VjcOfPEdwUPEvbnR8vw&token_type=bearer&expires_in=1209600&state=Qvlzg__CCwjCjaqEOInQw0__FprOykwROuAciRgDlIQ1
and that's all.
How I get these parameters from the URL and process them?
If I change the base URL to any other action I get the "invalid_request" error caused by calling uri is different that redirect_uri.
So, how a client app gets the access token ?
Any help or clarification will be really helpful.

1. create class for Token
public class Token
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
2. Startup class
[assembly: OwinStartup(typeof(ProjectName.API.Startup))]
namespace ProjectName.API
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var oauthProvider = new OAuthAuthorizationServerProvider
{
OnGrantResourceOwnerCredentials = async context =>
{
if (context.UserName == "xyz" && context.Password == "xyz#123")
{
var claimsIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
claimsIdentity.AddClaim(new Claim("user", context.UserName));
context.Validated(claimsIdentity);
return;
}
context.Rejected();
},
OnValidateClientAuthentication = async context =>
{
string clientId;
string clientSecret;
if (context.TryGetBasicCredentials(out clientId, out clientSecret))
{
if (clientId == "xyz" && clientSecret == "secretKey")
{
context.Validated();
}
}
}
};
var oauthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/accesstoken"),
Provider = oauthProvider,
AuthorizationCodeExpireTimeSpan= TimeSpan.FromMinutes(1),
AccessTokenExpireTimeSpan=TimeSpan.FromMinutes(3),
SystemClock= new SystemClock()
};
app.UseOAuthAuthorizationServer(oauthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
}
}
}
3 . Add a controller
[Authorize]
public class TestController : ApiController
{
[Route("test")]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, "hello !");
}
}
4. Now check the authorization on the basis of the token
static void Main()
{
string baseAddress = "http://localhost:/";
// Start OWIN host
using (WebApp.Start<Startup>(url: baseAddress))
{
var client = new HttpClient();
var response = client.GetAsync(baseAddress + "test").Result;
Console.WriteLine(response);
Console.WriteLine();
var authorizationHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes("xyz:secretKey"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authorizationHeader);
var form = new Dictionary<string, string>
{
{"grant_type", "password"},
{"username", "xyz"},
{"password", "xyz#123"},
};
var tokenResponse = client.PostAsync(baseAddress + "accesstoken", new FormUrlEncodedContent(form)).Result;
var token = tokenResponse.Content.ReadAsAsync<Token>(new[] { new JsonMediaTypeFormatter() }).Result;
Console.WriteLine("Token issued is: {0}", token.AccessToken);
Console.WriteLine();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
var authorizedResponse = client.GetAsync(baseAddress + "test").Result;
Console.WriteLine(authorizedResponse);
Console.WriteLine(authorizedResponse.Content.ReadAsStringAsync().Result);
}
}

Related

ASP.NET Core Web API - How to include User Details and roles in in Login response

In ASP.NET Core-6 Web API, I have this code:
Models:
public class AuthResult
{
public string AccessToken { get; set; }
public string TokenType { get; set; }
public int ExpiresIn { get; set; }
public string RefreshToken { get; set; }
}
public class JwtTokenManager : IJwtTokenManager
{
private readonly JwtSettings _jwtSettings;
private readonly UserManager<ApplicationUser> _userManager;
private readonly TokenValidationParameters _tokenValidationParameters;
public async Task<AuthResult> GenerateClaimsTokenAsync(string username, CancellationToken cancellationToken)
{
var user = await _userManager.FindByNameAsync(username);
var roles = await _userManager.GetRolesAsync(user);
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), // TODO: encrypt user id for added security
new Claim(ClaimTypes.Name, username),
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddMinutes(5)).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
}),
Expires = DateTime.UtcNow.Add(_jwtSettings.Expiration),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var refreshTokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), // TODO: encrypt user id for added security
new Claim(ClaimTypes.Name, username),
new Claim(JwtRegisteredClaimNames.Iss, _jwtSettings.Issuer),
new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
}),
Expires = DateTime.UtcNow.Add(_jwtSettings.Expiration),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
// Create JWT tokens
var token = tokenHandler.CreateToken(tokenDescriptor);
var refreshtoken = tokenHandler.CreateToken(refreshTokenDescriptor);
return new AuthResult
{
AccessToken = tokenHandler.WriteToken(token),
TokenType = "Bearer",
ExpiresIn = _jwtSettings.Expiration.Seconds,
RefreshToken = tokenHandler.WriteToken(refreshtoken)
};
}
}
Identity:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MobileNumber { get; set; }
}
UserDto:
public class UserDto
{
public long Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Email { get; set; }
public string UserName { get; set; }
}
Then, finally I have SignInCommandHandler
public class SignInCommandHandler : IRequestHandler<SignInCommand, AuthResult>
{
private readonly ISignInManager _signInManager;
private readonly IJwtTokenManager _jwtTokenManager;
public SignInCommandHandler(ISignInManager signInManager, IJwtTokenManager jwtTokenManager)
{
_signInManager = signInManager;
_jwtTokenManager = jwtTokenManager;
}
public async Task<AuthResult> Handle(SignInCommand request, CancellationToken cancellationToken)
{
// validate username & password
var result = await _signInManager.PasswordSignInAsync(request.Username, request.Password, false, false);
// Throw exception if credential validation failed
if (!result.Successful)
{
throw new UnauthorizedException("Invalid username or password.");
}
// Generate JWT token response if validation successful
AuthResult response = await _jwtTokenManager.GenerateClaimsTokenAsync(request.Username, cancellationToken);
return response;
}
}
I have done something like:
var user = await _userManager.FindByNameAsync(username);
var roles = await _userManager.GetRolesAsync(user);
in JwtTokenManager
How do I involve the detail of the logged in user and his roles in the
return response?
Thank you

IdentityServer4 using ApiKey or Basic authentication directly to API

I am using IdentityServer4 to have my customers login and access web pages and api's from JavaScript and it is working well. However, there is a new requirement that rather than using username and password to get an access token from the identity server and then using that to access the api with Bearer authentication... I would need to call the api directly with a "Basic" authentication header and the api would confirm the identity with the identity server. Similar to the code below that is used to access the ZenDesk api...
using (var client = new HttpClient())
{
var username = _configuration["ZenDesk:username"];
var password = _configuration["ZenDesk:password"];
var token = Convert.ToBase64String(Encoding.ASCII.GetBytes(username + ":" + password));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", token);
var response = client.PostAsync("https://...
Any help on how I would implement this? Is there anything built into IdentityServer4 that would accommodate this approach? I am using .Net Core 3.1 for both the api server and for the identity server.
Another (seemingly common) approach would be to generate an api key for each user and then allow the user to call the api like this...
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(URL_HOST_API);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("ApiKey", "123456456123456789");
…
}
Thoughts?
It turns out that IdentityServer4 does not have built in support for ApiKeys... but .Net Core 3.1 has IAuthorizationHandler which allows you to roll your own authorization for ApiKeys and insert it into the flow with dependancy injection.
The way I did it was... to have an ApiKey and an ApiKeySecret. This way the UserId is not being exposed at all... I have a database table on my IdentityServer4 (Server C) called ApiKey that contains the fields (ApiKeyId, UserId, ApiKey, and ApiKeySecret)... ApiKeySecret is a one way hash like a password.
I added an ApiKeyController to my IdentityServer4 project (Server C)... this will allow an ApiRequest to Validate the ApiKeys.
So... to follow the flow:
Server A: ThirdParty .Net Core 3.1 Web Server
Server B: MyApiServer .Net Core 3.1 Web Server
Server C: MyIdentityerServer4 .Net Core 3.1 IndentityServer4
Based on a request (likely from a browser) to Server A.
Server A then calls my API (Server B) with an ApiKey and an ApiKeySecret in the headers:
using (var client = new HttpClient())
{
var url = _configuration["MyApiUrl"] + "/WeatherForecast";
var apiKey = _configuration["MyApiKey"];
var apiKeySecret = _configuration["MyApiKeySecret"];
client.DefaultRequestHeaders.Add("x-api-key", apiKey);
client.DefaultRequestHeaders.Add("secret-api-key", apiKeySecret);
var response = client.GetAsync(url).Result;
if (response.IsSuccessStatusCode)
{
var contents = response.Content.ReadAsStringAsync().Result;
return contents;
}
return "StatusCode = " + response.StatusCode;
}
On my API Server (Server B) I have added the following class which, if the [Authorize] category is set for a url, will validate the ApiKeys in the Header by calling the ApiKeyController on the IdentityServer4 (Server C) and putting the return value (UserId) on the HttpContext.Items collection.
Basically the system already defines an IAuthorizationHandler for (I believe) services.AddAuthentication("Bearer")... so when adding a second one (or more)... they will each be called, if one returns Succeeded no more will be call... if they all fail, then the [Authorized] will fail.
public class ApiKeyAuthorizationHandler : IAuthorizationHandler
{
private readonly ILogger<ApiKeyAuthorizationHandler> _logger;
private readonly IConfiguration _configuration;
private readonly IHttpContextAccessor _httpContextAccessor;
public ApiKeyAuthorizationHandler(
ILogger<ApiKeyAuthorizationHandler> logger,
IConfiguration configuration,
IHttpContextAccessor httpContextAccessor
)
{
_logger = logger;
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
}
public Task HandleAsync(AuthorizationHandlerContext context)
{
try
{
string apiKey = _httpContextAccessor.HttpContext.Request.Headers["x-api-key"].FirstOrDefault();
string apiKeySecret = _httpContextAccessor.HttpContext.Request.Headers["secret-api-key"].FirstOrDefault();
if (apiKey != null && apiKeySecret != null)
{
if (Authorize(apiKey, apiKeySecret))
SetSucceeded(context);
}
return Task.CompletedTask;
}
catch (Exception ex)
{
_logger.LogError(ex, "HandleAsync");
return Task.CompletedTask;
}
}
public class ValidateResponse
{
public string UserId { get; set; }
}
private bool Authorize(string apiKey, string apiKeySecret)
{
try
{
using (var client = new HttpClient())
{
var url = _configuration["AuthorizationServerUrl"] + "/api/ApiKey/Validate";
var json = JsonConvert.SerializeObject(new
{
clientId = "serverb-api", // different ApiKeys for different clients
apiKey = apiKey,
apiKeySecret = apiKeySecret
});
var response = client.PostAsync(url, new StringContent(json, Encoding.UTF8, "application/json")).Result;
if (response.IsSuccessStatusCode)
{
var contents = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<ValidateResponse>(contents);
_httpContextAccessor.HttpContext.Items.Add("UserId", result.UserId);
}
return response.IsSuccessStatusCode;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Authorize");
return false;
}
}
private void SetSucceeded(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
context.Succeed(requirement);
}
}
}
I also need to add the following to Startup.cs on Server B:
services.AddSingleton<IAuthorizationHandler, ApiKeyAuthorizationHandler>();
And for completeness my code on IdentityServer4 (Server C):
ApiKeyController.cs
using System;
using MyIdentityServer.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace MyIdentityServer
{
[Route("api/[controller]")]
[ApiController]
public class ApiKeyController : ControllerBase
{
private readonly ILogger<ApiKeyController> _logger;
private readonly IApiKeyService _apiKeyService;
public ApiKeyController(
IApiKeyService apiKeyService,
ILogger<ApiKeyController> logger
)
{
_apiKeyService = apiKeyService;
_logger = logger;
}
public class ValidateApiKeyRequest
{
public string ClientId { get; set; }
public string ApiKey { get; set; }
public string ApiKeySecret { get; set; }
}
[HttpPost("Validate")]
[AllowAnonymous]
[Consumes("application/json")]
public IActionResult PostBody([FromBody] ValidateApiKeyRequest request)
{
try
{
(var clientId, var userId) = _apiKeyService.Verify(request.ApiKey, request.ApiKeySecret);
if (request.ClientId == clientId && userId != null)
return Ok(new { UserId = userId });
// return new JsonResult(new { UserId = userId }); // maybe also return claims for client / user
return Unauthorized();
}
catch (Exception ex)
{
_logger.LogError(ex, "HandleValidateApiKey apiKey={request.ApiKey} apiKeySecret={request.ApiKeySecret}");
return Unauthorized();
}
}
public class GenerateApiKeyRequest
{
public string ClientId { get; set; }
public string UserId { get; set; }
}
[HttpPost("Generate")]
[AllowAnonymous]
public IActionResult Generate(GenerateApiKeyRequest request)
{
// generate and store in database
(var apiKey, var apiKeySecret) = _apiKeyService.Generate(request.ClientId, request.UserId);
return new JsonResult(new { ApiKey = apiKey, ApiKeySecret = apiKeySecret });
}
}
}
ApiKeyService.cs
using Arch.EntityFrameworkCore.UnitOfWork;
using EQIdentityServer.Data.Models;
using System;
using System.Security.Cryptography;
public namespace MyIndentityServer4.Services
public interface IApiKeyService
{
(string, string) Verify(string apiKey, string apiKeySecret);
(string, string) Generate(string clientId, string userId);
}
public class ApiKeyService : IApiKeyService
{
IUnitOfWork _unitOfWork;
public ApiKeyService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public (string, string) Verify(string apiKey, string apiKeySecret)
{
var repoApiKey = _unitOfWork.GetRepository<ClientUserApiKey>();
var item = repoApiKey.GetFirstOrDefault(predicate: p => p.ApiKey == apiKey);
if (item == null)
return (null, null);
if (!OneWayHash.Verify(item.ApiKeySecretHash, apiKeySecret))
return (null, null);
return (item?.ClientId, item?.UserId);
}
public (string, string) Generate(string clientId, string userId)
{
var repoApiKey = _unitOfWork.GetRepository<ClientUserApiKey>();
string apiKey = null;
string apiKeySecret = null;
string apiKeySecretHash = null;
var key = new byte[30];
using (var generator = RandomNumberGenerator.Create())
generator.GetBytes(key);
apiKeySecret = Convert.ToBase64String(key);
apiKeySecretHash = OneWayHash.Hash(apiKeySecret);
var item = repoApiKey.GetFirstOrDefault(
predicate: p => p.ClientId == clientId && p.UserId == userId
);
if (item != null)
{
// regenerate only secret for existing clientId/userId
apiKey = item.ApiKey; // item.ApiKey = apiKey; // keep this the same, or you could have multiple for a clientId if you want
item.ApiKeySecretHash = apiKeySecretHash;
repoApiKey.Update(item);
}
else
{
// new for user
key = new byte[30];
while (true)
{
using (var generator = RandomNumberGenerator.Create())
generator.GetBytes(key);
apiKey = Convert.ToBase64String(key);
var existing = repoApiKey.GetFirstOrDefault(
predicate: p => p.ApiKey == apiKey
);
if (existing == null)
break;
}
item = new ClientUserApiKey() { ClientId = clientId, UserId = userId, ApiKey = apiKey, ApiKeySecretHash = apiKeySecretHash };
repoApiKey.Insert(item);
}
_unitOfWork.SaveChanges();
return (apiKey, apiKeySecret);
}
}
My Model:
public class ClientUserApiKey
{
public long ClientUserApiKeyId { get; set; }
[IndexColumn("IX_ApiKey_ClientIdUserId", 0)]
public string ClientId { get; set; }
[IndexColumn("IX_ApiKey_ClientIdUserId", 1)]
public string UserId { get; set; }
[IndexColumn]
public string ApiKey { get; set; }
[StringLength(128)]
public string ApiKeySecretHash { get; set; }
}
And, then my WeatherForecastController can get the logged in user one of two ways... via a Bearer access_token or my ApiKeys:
string userId = null;
if (User?.Identity.IsAuthenticated == true)
userId = User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier).Value;
else
userId = this.HttpContext.Items["UserId"]?.ToString(); // this comes from ApiKey validation
#nahidf has a good approach, i will just add that you can also use a custom Resource Owner Password validator. It grants you the possibility of implementing your own password validator like so
[edit] : This is for you to don't have to use identity model
public class ApiResourceOwnerPasswordValidation : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(a =>
(a.UserName == context.UserName || a.Email.ToLower().Trim() == context.UserName.ToLower().Trim())
&& context.Password.VerifyHashedPassword(a.Password));
if (user != null)
{
context.Result = new GrantValidationResult(
subject: user.Id.ToString(),
authenticationMethod: "custom",
claims: AssambleClaims(user)
);
user.LoggedIn = DateTime.UtcNow;
await _dbContext.CommitAsync();
}
else
{
context.Result = new GrantValidationResult(
TokenRequestErrors.InvalidGrant,
"Invalid credentials");
}
}
}
You can implement OAuth password grant, as this is a server to server authentication.
To implement follow these steps:
Register a client on IdentityServer for your API project, here is an example
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "resourceownerclient",
ClientSecrets= new List<Secret> { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
AccessTokenType = AccessTokenType.Jwt,
AccessTokenLifetime = 120, //86400,
IdentityTokenLifetime = 120, //86400,
UpdateAccessTokenClaimsOnRefresh = true,
SlidingRefreshTokenLifetime = 30,
AllowOfflineAccess = true,
RefreshTokenExpiration = TokenExpiration.Absolute,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
AlwaysSendClientClaims = true,
Enabled = true,
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess
}
}
Install IdentityModel package on API project https://www.nuget.org/packages/IdentityModel/
Request a token on API using username/password received on header like this:
private static async Task<TokenResponse> RequestTokenAsync(string user, string password)
{
var _httpClient = new HttpClient();
var _disco = await HttpClientDiscoveryExtensions.GetDiscoveryDocumentAsync(
_httpClient,
_stsUrl);
var response = await _httpClient.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = _disco.TokenEndpoint,
ClientId = "resourceownerclient",
ClientSecret = "secret",
Scope = "email openid offline_access",
UserName = user,
Password = password
});
return response;
}
Check response - IsError property to make sure the request was successful
If you need to customize any thing on IdentityServer for password grant, you need to implement IResourceOwnerPasswordValidator,read more here

AspNetUsers custom columns don't appear in intellisense

Am trying to create a user database that I can modify to suit what my users will need to submit when registering for my service, I've created the database and am able to modify it and include whatever columns I want but I can't seem to access them in my c# code, the only fields that appear are those native to AspNetUsers, I've tried looking at similar questions but I can't seem to grasp the concepts specific to what I need, anyone that can help me get some clarity on this cause am a bit new to working with IdentityUser.
//Registration/Login
public class Identify : IIdentify
{
private readonly UserManager<IdentityUser> _manager;
private readonly Mystery _jwtset;
private readonly DataContext _personality;
public Identify(UserManager<IdentityUser> userManager, Mystery jW, DataContext users)
{
_manager = userManager;
_jwtset = jW;
_personality = users;
}
public async Task<Authentication_result> RegisterAsync(string email, string password, string Username)
{
var exists = await _manager.FindByEmailAsync(email);
if (exists != null)
{
return new Authentication_result
{
Errors = new[] { "User with this email already exists" }
};
}
var newPerson = new IdentityUser()
{
Email = email,
UserName = Username
};
var Creation = await _manager.CreateAsync(newPerson, password);
if (!Creation.Succeeded)
{
return new Authentication_result
{
Errors = new[] { "Invalid user!" }
};
}
return Generate_Authentication_Result(newPerson);
}
public async Task<Authentication_result> LoginAsync(string email, string Password)
{
var exists = await _manager.FindByEmailAsync(email);
if (exists == null)
{
return new Authentication_result
{
Errors = new[] { "User does not exists" }
};
}
var pass_validation = await _manager.CheckPasswordAsync(exists, Password);
if (!pass_validation)
{
return new Authentication_result
{
Errors = new[] { "f78wrvep034rf wrong" }
};
}
return Generate_Authentication_Result(exists);
}
private Authentication_result Generate_Authentication_Result(IdentityUser newPerson)
{
var Tokenhandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtset.Secret);
var TokenDescripter = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, newPerson.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Email, newPerson.Email),
new Claim("id",newPerson.Id)
}),
Expires = DateTime.UtcNow.AddHours(2),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = Tokenhandler.CreateToken(TokenDescripter);
return new Authentication_result
{
Success = true,
Token = Tokenhandler.WriteToken(token)
};
}
}
//Controller for the above
public class IdentifyMe : Controller
{
private readonly IIdentify _identify;
public IdentifyMe(IIdentify identifying)
{
_identify = identifying;
}
[HttpPost(Api_Routes.Identity.Register)]
public async Task<IActionResult> Register(UserRegistration register)
{
if (!ModelState.IsValid)
{
return BadRequest(new Unauthenticated
{
Errors = ModelState.Values.SelectMany(x => x.Errors.Select(xx => xx.ErrorMessage))
});
}
var authresponce = await _identify.RegisterAsync(register.Email, register.Password, register.User_Name);
if (!authresponce.Success)
{
return BadRequest(new Unauthenticated
{
Errors = authresponce.Errors
});
}
return Ok(new Authenticated
{
Token = authresponce.Token
});
}
[HttpPost(Api_Routes.Identity.Login)]
public async Task<IActionResult> LoginAsync(User_login login)
{
var authresponce = await _identify.LoginAsync(login.email, login.Password);
if (!authresponce.Success)
{
return BadRequest(new Unauthenticated
{
Errors = authresponce.Errors
});
}
return Ok(new Authenticated
{
Token = authresponce.Token
});
}
}
//Domain object, these are the values I would like to be able to access
public class Users : IdentityUser
{
public string PreferredNet { get; set; }
public int Inactive { get; set; }
public int Active { get; set; }
public int Max_Return { get; set; }
public DateTime Time { get; set; }
}
//Other controller
public ActionResult <IEnumerable<Time_dto>> Getitem(string usernum, int amt, string user, string server)
{
var Total = caller.Getusers();
//This is my attempt to acces the domain object, pitcture below[![Intelisense does not display fields in domain object][1]][1]
var container=Total.Select(x=>x.)
var totalin = _digital.Map<IEnumerable<User_dto>>(Total).Count(x => x.PreferredNet == user);
var totalout= _digital.Map<IEnumerable<User_dto>>(Total).Count(x=>x.PreferredNet== server);
int factor = 1;
var HCD = caller.rates(factor, user, server);
var result = shift;
int retrive = caller.Total(amt, user, server, HCD);
var serials = caller.cards(retrive);
int differential = retrive > serials.Sum() ? retrive serials.Sum() : serials.Sum() - retrive;
int number = serials.Count();
IEnumerable<int> Real_cards=new List<int>();
}
```
[1]: https://i.stack.imgur.com/HEenG.png

"AmbiguousActionException: Multiple actions matched" when using only 1 GET

I'm getting this error even though I only have one GET on the page. So I'm thoroughly confused.
Here's what the whole login page looks like:
public class LoginModel : PageModel
{
private UserAuthenticationService _userAuthenticationService { get; set; }
private ClaimService _claimService { get; set; }
public LoginModel(UserAuthenticationService userAuthenticationService, ClaimService claimService)
{
_userAuthenticationService = userAuthenticationService;
_claimService = claimService;
}
public async Task OnGetAsync()
{
var user = _userAuthenticationService.Login();
if (user == null)
{
//TODO: Login failed
}
else
{
var claims = _claimService.GetClaimsFromUserModel(user);
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties()
{
ExpiresUtc = DateTime.UtcNow.AddDays(6),
IsPersistent = true,
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), authProperties);
}
}
And here's the error. I only have one OnGet, so I don't know why it's saying there are multiple. Any ideas?

User specific secret for JWT in OWIN based on request parameter

I have created a sample identity service. It reads a secret key from config file.
ConfigurationManager.AppSettings["as:AudienceSecret"]
I need to modify it to read the secret from a database, based on the incoming request’s body parameter (form["CurrentUser"]). How can we do this?
Startup.cs configuration
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
string issuer = ConfigurationManager.AppSettings["Issuer"];
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(issuer)
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
string issuer = ConfigurationManager.AppSettings["Issuer"];
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
GrantResourceOwnerCredentials in CustomOAuthProvider
public override async Task
GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = ConfigurationManager.AppSettings["AllowedOrigin"];
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = null;
try
{
user = await userManager.FindAsync(context.UserName, context.Password);
}
catch (Exception ex)
{
string result = ex.Message;
string innerText = ex.InnerException.ToString();
}
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var form = await context.Request.ReadFormAsync();
var loggedinUserName = form["CurrentUser"];
string practice = null;
if (!String.IsNullOrWhiteSpace(loggedinUserName))
{
ApplicationUser loggedinUserObj = userManager.FindByName(loggedinUserName);
string loggedinUserID = loggedinUserObj == null ? "" : loggedinUserObj.Id;
if (loggedinUserID != null)
{
ProvidersBL providersBL = new ProvidersBL();
practice = providersBL.GetPracticeForUser(loggedinUserID);
}
}
practice = practice ?? "Undefined";
loggedinUserName = loggedinUserName ?? "Undefined";
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, loggedinUserName));
claims.Add(new Claim("Practice", practice));
var oAuthIdentity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
Following works. Not sure whether this is the best solution though.
Note: I later decided not use different key for different users. The key will be same for all users. But there will be a user specific key inserted for each user inside the token as a claim. This key will be refreshed in the database once the user used it once. For the next time, the userkey will be different in the token, for the same user.
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;
private ProvidersBL providerBL;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
providerBL = new ProvidersBL();
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string userName = String.Empty;
var userNameClaim = data.Identity.Claims.FirstOrDefault(claim => claim.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name");
if (userNameClaim != null)
{
userName = userNameClaim.Value;
}
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
string symmetricKeyAsBase64 = string.Empty;
symmetricKeyAsBase64 = providerBL.GetKeyForUser(userName);
if (String.IsNullOrWhiteSpace(symmetricKeyAsBase64))
{
symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
}
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}

Categories