Saving settings/variables/permissions in memory instead of looking them up on each API call? - c#

I have an API set up that allows placing orders, looking up product info, reporting, etc. Each API key has specific permissions on which controllers/methods they can or can't access, as well as fields that should be omitted. Unfortunately right now I have this hardcoded in a dictionary class and would like to instead pull these permissions from a database.
The problem is I don't want to call the database to lookup permissions every time a method is called to avoid a performance hit. Is there a way to POST these settings/permissions any time there's a change (using an admin page) and have the API "remember" them in memory in some sort of dictionary? Also when restarting the API I'm guessing these are cleared so I would need a way to pull this information when the API initializes. Not sure what the best way to design this is, any suggestions are helpful thanks.

can't you just use standard roles based authorization?
this is what I followed when I set mine up https://weblog.west-wind.com/posts/2021/Mar/09/Role-based-JWT-Tokens-in-ASPNET-Core
[Authorize(Roles = "admin")]
[HttpPost]
public async Task<IActionResult> Post(RoomDelegate roomDelegate) =>
HandleResult(await Mediator.Send(new Post.Command { RoomDelegate = roomDelegate }));
store your roles in the tokens claims.
public class TokenService
{
private readonly IConfiguration _config;
private readonly UserManager<AppUser> _userManager;
public TokenService(IConfiguration config, UserManager<AppUser> userManager)
{
_config = config;
_userManager = userManager;
}
public IConfiguration Config { get; }
public async Task<string> CreateToken(AppUser user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName ),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Email, user.Email),
};
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["TokenKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescription = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(10),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescription);
return tokenHandler.WriteToken(token);
}
public RefreshToken GenerateRefreshToken()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return new RefreshToken { Token = Convert.ToBase64String(randomNumber) };
}
}

Related

JWT bearer token authorization with no database access, use service/repository?

I have an API with controller-service-repository pattern.
I want to implement JWT bearer token authorization so I created an AuthenticationController.
The rest of my application flows from the controller which calls the service which calls the repository.
However, I am not accessing the database for authentication and I'm only accessing configuration data so I only have a controller and no service/repository.
Is it recommended to implement this way or is it better to include the service/repository even though I'm not accessing the database to make it consistent?
This is my AuthenticationController:
[Route("api/Authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
protected readonly IConfiguration _config;
public IConfiguration Configuration { get { return _config; } }
public AuthenticationController(IConfiguration config)
{
_config = config;
}
[Route("Login")]
[AllowAnonymous]
[HttpPost]
public IActionResult Login([FromBody] UserLogin userLogin)
{
var user = AuthenticateUser(userLogin);
if (user != null)
{
var token = GenerateToken();
return Ok(token);
}
return NotFound("User not found");
}
private UserModel AuthenticateUser(UserLogin userLogin)
{
UserModel user = new UserModel()
{
ClientID = _config["Claims:ClientID"],
ClientSecret = _config["Claims:ClientSecret"]
};
if (user.ClientID == userLogin.ClientID &&
user.ClientSecret == userLogin.ClientSecret)
{
return user;
}
return null;
}
private string GenerateToken()
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim("client_id", _config["Claims:ClientID"]),
new Claim("client_secret", _config["Claims:ClientSecret"]),
new Claim("grant_type", _config["Claims:GrantType"]),
new Claim("scope", _config["Claims:Scope"]),
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Audience"],
claims,
expires: DateTime.Now.AddMinutes(15),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
There certainly is no need to use repository here. You could create just the service alone.
You don't always have to follow service/repository pattern, unless you really have need for such level of abstraction.
In fact in many cases you can just get away with IMediator's CommandHandlers with injected DbContext. Read more here: https://softwareengineering.stackexchange.com/a/220126

How to mock AuthenticationStateProvider

I have a class that takes an AuthenticationStateProvider in the constructor. I'd like to write unit test and mock this.
I'd like to be able to set what user is returned from the call GetAuthenticationStateAsync.
const string userId = "123";
const string userName = "John Doe";
const string email = "john.doe#test.com";
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, email),
};
var identity = new Mock<ClaimsIdentity>(claims);
var principal = new Mock<ClaimsPrincipal>(identity.Object);
var mockOfAuthenticationStateProvider = new Mock<AuthenticationStateProvider>();
var mockOfAuthState = new Mock<AuthenticationState>();
mockOfAuthenticationStateProvider.Setup(p =>
p.GetAuthenticationStateAsync()).Returns(Task.FromResult(mockOfAuthState.Object));
I get this errormessage:
Testfunction threw exception: System.ArgumentException: Can not
instantiate proxy of class:
Microsoft.AspNetCore.Components.Authorization.AuthenticationState.
Could not find a parameterless constructor. (Parameter
'constructorArguments') ---> System.MissingMethodException:
Constructor on type 'Castle.Proxies.AuthenticationStateProxy' not
found.
AuthenticationStateProvider is abstract, so if you can't mock it you can create an implementation of it, like this:
public class FakeAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ClaimsPrincipal _principal;
public FakeAuthenticationStateProvider(ClaimsPrincipal principal)
{
_principal = principal;
}
// This static method isn't really necessary. You could call the
// constructor directly. I just like how it makes it more clear
// what the fake is doing within the test.
public static FakeAuthenticationStateProvider ForPrincipal(ClaimsPrincipal principal)
{
return new FakeAuthenticationStateProvider(principal);
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
return Task.FromResult(new AuthenticationState(_principal));
}
}
You can set it up like this:
const string userId = "123";
const string userName = "John Doe";
const string email = "john.doe#test.com";
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, email),
};
// These don't need to be mocks. If they are the test likely
// won't behave correctly.
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
var authenticationStateProvider =
FakeAuthenticationStateProvider.ForPrincipal(principal);
and then pass it to any other class that depends on AuthenticationStateProvider.
When we create a fake implementation instead of a mock it's reusable and also easier to set up, so it keeps the test a little bit smaller.
For example, if the reason you're setting this up is so that the mock/fake returns a user with certain claims, you could have the fake just take a set of claims in its constructor. Then the constructor does the rest of the work, making your test even smaller.
For example,
public class FakeAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ClaimsPrincipal _principal;
public FakeAuthenticationStateProvider(ClaimsPrincipal principal)
{
_principal = principal;
}
public static FakeAuthenticationStateProvider ForPrincipal(ClaimsPrincipal principal)
{
return new FakeAuthenticationStateProvider(principal);
}
public static FakeAuthenticationStateProvider ThatReturnsClaims(params Claim[] claims)
{
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
return new FakeAuthenticationStateProvider(principal);
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
return Task.FromResult(new AuthenticationState(_principal));
}
}
Now your test can have less clutter:
var claims = new []
{
new Claim(ClaimTypes.NameIdentifier, "123"),
new Claim(ClaimTypes.Name, "John Doe"),
new Claim(ClaimTypes.Email, "john.doe#test.com"),
};
var authenticationStateProvider = FakeAuthenticationStateProvider.ThatReturnsClaims(claims);
I usually end up with a folder called "Fakes" in my test project for them.

Generate access token with IdentityServer3 without password

How to manually generate access_token from server without password?
I want to allow super admins login as users and look at their problems and see the problems by their eyes, so i need user access_token. i already see this question but didn't help me in IdentityServer3.
first create a custom grant named loginBy
public class LoginByGrant : ICustomGrantValidator
{
private readonly ApplicationUserManager _userManager;
public string GrantType => "loginBy";
public LoginByGrant(ApplicationUserManager userManager)
{
_userManager = userManager;
}
public async Task<CustomGrantValidationResult> ValidateAsync(ValidatedTokenRequest request)
{
var userId = Guid.Parse(request.Raw.Get("user_id"));
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
return await Task.FromResult<CustomGrantValidationResult>(new CustomGrantValidationResult("user not exist"));
var userClaims = await _userManager.GetClaimsAsync(user.Id);
return
await Task.FromResult<CustomGrantValidationResult>(new CustomGrantValidationResult(user.Id.ToString(), "custom", userClaims));
}
}
then add this custom grant in identity startup class
factory.CustomGrantValidators.Add(
new Registration<ICustomGrantValidator>(resolver => new LoginByGrant(ApplicaionUserManager)));
and finally in your api
public async Task<IHttpActionResult> LoginBy(Guid userId)
{
var tokenClient = new TokenClient(Constants.TokenEndPoint, Constants.ClientId, Constants.Secret);
var payload = new { user_id = userId.ToString() };
var result = await tokenClient.RequestCustomGrantAsync("loginBy", "customScope", payload);
if (result.IsError)
return Ok(result.Json);
return Ok(new { access_token = result.AccessToken, expires_in = result.ExpiresIn});
}
this is for identityServer3 but for identityServer4 it is pretty similar

IdentityServer4: How to manually create a JWT for a user? [duplicate]

I have created ASP.NET Core WebApi protected with IdentityServer4 using ROPC flow (using this example: https://github.com/robisim74/AngularSPAWebAPI).
How to manually generate access_token from the server without password?
[HttpPost("loginas/{id}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> LoginAs(int id, [FromServices] ITokenService TS,
[FromServices] IUserClaimsPrincipalFactory<ApplicationUser> principalFactory,
[FromServices] IdentityServerOptions options)
{
var Request = new TokenCreationRequest();
var User = await userManager.FindByIdAsync(id.ToString());
var IdentityPricipal = await principalFactory.CreateAsync(User);
var IdServerPrincipal = IdentityServerPrincipal.Create(User.Id.ToString(), User.UserName, IdentityPricipal.Claims.ToArray());
Request.Subject = IdServerPrincipal;
Request.IncludeAllIdentityClaims = true;
Request.ValidatedRequest = new ValidatedRequest();
Request.ValidatedRequest.Subject = Request.Subject;
Request.ValidatedRequest.SetClient(Config.GetClients().First());
Request.Resources = new Resources(Config.GetIdentityResources(), Config.GetApiResources());
Request.ValidatedRequest.Options = options;
Request.ValidatedRequest.ClientClaims = IdServerPrincipal.Claims.ToArray();
var Token = await TS.CreateAccessTokenAsync(Request);
Token.Issuer = "http://" + HttpContext.Request.Host.Value;
var TokenValue = await TS.CreateSecurityTokenAsync(Token);
return Ok(TokenValue);
}
For a newly released IdentityServer 2.0.0 the code needs some modifications:
[HttpPost("loginas/{id}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> LoginAs(int id, [FromServices] ITokenService TS,
[FromServices] IUserClaimsPrincipalFactory<ApplicationUser> principalFactory,
[FromServices] IdentityServerOptions options)
{
var Request = new TokenCreationRequest();
var User = await userManager.FindByIdAsync(id.ToString());
var IdentityPricipal = await principalFactory.CreateAsync(User);
var IdentityUser = new IdentityServerUser(User.Id.ToString());
IdentityUser.AdditionalClaims = IdentityPricipal.Claims.ToArray();
IdentityUser.DisplayName = User.UserName;
IdentityUser.AuthenticationTime = System.DateTime.UtcNow;
IdentityUser.IdentityProvider = IdentityServerConstants.LocalIdentityProvider;
Request.Subject = IdentityUser.CreatePrincipal();
Request.IncludeAllIdentityClaims = true;
Request.ValidatedRequest = new ValidatedRequest();
Request.ValidatedRequest.Subject = Request.Subject;
Request.ValidatedRequest.SetClient(Config.GetClients().First());
Request.Resources = new Resources(Config.GetIdentityResources(), Config.GetApiResources());
Request.ValidatedRequest.Options = options;
Request.ValidatedRequest.ClientClaims = IdentityUser.AdditionalClaims;
var Token = await TS.CreateAccessTokenAsync(Request);
Token.Issuer = HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value;
var TokenValue = await TS.CreateSecurityTokenAsync(Token);
return Ok(TokenValue);
}
Use this:
http://docs.identityserver.io/en/latest/topics/tools.html
Use this tool that come with identity server:
Declare it in the constructor, to receive by dependecy injection.
IdentityServer4.IdentityServerTools _identityServerTools
var issuer = "http://" + httpRequest.Host.Value;
var token = await _identityServerTools.IssueJwtAsync(
30000,
issuer,
new System.Security.Claims.Claim[1]
{
new System.Security.Claims.Claim("cpf", cpf)
}
);
Here is another way to achieve this:
first create a custom grant named loginBy
public class LoginByGrant : ICustomGrantValidator
{
private readonly ApplicationUserManager _userManager;
public string GrantType => "loginBy";
public LoginByGrant(ApplicationUserManager userManager)
{
_userManager = userManager;
}
public async Task<CustomGrantValidationResult> ValidateAsync(ValidatedTokenRequest request)
{
var userId = Guid.Parse(request.Raw.Get("user_id"));
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
return await Task.FromResult<CustomGrantValidationResult>(new CustomGrantValidationResult("user not exist"));
var userClaims = await _userManager.GetClaimsAsync(user.Id);
return
await Task.FromResult<CustomGrantValidationResult>(new CustomGrantValidationResult(user.Id.ToString(), "custom", userClaims));
}
}
then add this custom grant in identity startup class
factory.CustomGrantValidators.Add(
new Registration<ICustomGrantValidator>(resolver => new LoginByGrant(ApplicaionUserManager)));
and finally in your api
public async Task<IHttpActionResult> LoginBy(Guid userId)
{
var tokenClient = new TokenClient(Constants.TokenEndPoint, Constants.ClientId, Constants.Secret);
var payload = new { user_id = userId.ToString() };
var result = await tokenClient.RequestCustomGrantAsync("loginBy", "customScope", payload);
if (result.IsError)
return Ok(result.Json);
return Ok(new { access_token = result.AccessToken, expires_in = result.ExpiresIn});
}
Further to my comment on your original question. Implement an impersonation feature within the implicit/hybrid flow. If a user is determined to be a "super admin" then present them with an additional step after authentication that lets them enter/select the account they wish to impersonate. Once that's done simply establish the session on the identity server as the selected user (and possibly store additional claims denoting that it is an impersonated session and who is doing the impersonation). Any tokens will then be issued as if you were that user and all without having to know the password.
Additionally if you wish to create tokens yourself have a look at the ITokenCreationService provided by IdSrv4. You can inject that into your own controller/service/whatever and use CreateTokenAsync(Token token) to generate a signed JWT with any claims you like.
A little late to answer.
in my case of Generating Access Token Without Password there was another identity server as an organization sso, and our implementation already used IdentityServer, so we need to get user token from second IdentityServer (after user login and redirected to our app), extract sub, check if it is already existed(if not insert into our local IdentityServer), finally select user and use newly grant to get token for user.
your client should have this granttype as Allowed Grant types (here userexchange):
see: identity server docs, or duende docs for more information
public class TokenExchangeGrantValidator : IExtensionGrantValidator {
protected readonly UserManager<ToranjApplicationUser> _userManager;
private readonly IEventService _events;
public TokenExchangeGrantValidator(ITokenValidator validator, IHttpContextAccessor httpContextAccessor, UserManager<ToranjApplicationUser> userManager
, IEventService events) {
_userManager = userManager;
_events = events;
}
public async Task ValidateAsync(ExtensionGrantValidationContext context) {
var userName = context.Request.Raw.Get("uname");
if (string.IsNullOrEmpty(userName)) {
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
var user = await _userManager.FindByNameAsync(userName);
// or use this one, if you are sending userId
//var user = await _userManager.FindByIdAsync(userId);
if (null == user) {
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName, false, context.Request.ClientId));
var customResponse = new Dictionary<string, object>
{
{OidcConstants.TokenResponse.IssuedTokenType, OidcConstants.TokenTypeIdentifiers.AccessToken}
};
context.Result = new GrantValidationResult(
subject: user.Id.ToString(),
authenticationMethod: GrantType,
customResponse: customResponse);
}
public string GrantType => "userexchange";
}
in your startup's ConfigureServices after var builder = services.AddIdentityServer(...) add your newly created class.
builder.AddExtensionGrantValidator<TokenExchangeGrantValidator>();
calling it to get token is as simple as:
POST /connect/token
grant_type=userexchange&
scope=yourapi&
uname=yourusername&
client_id=yourClientId
client_secret=secret

Generate access token with IdentityServer4 without password

I have created ASP.NET Core WebApi protected with IdentityServer4 using ROPC flow (using this example: https://github.com/robisim74/AngularSPAWebAPI).
How to manually generate access_token from the server without password?
[HttpPost("loginas/{id}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> LoginAs(int id, [FromServices] ITokenService TS,
[FromServices] IUserClaimsPrincipalFactory<ApplicationUser> principalFactory,
[FromServices] IdentityServerOptions options)
{
var Request = new TokenCreationRequest();
var User = await userManager.FindByIdAsync(id.ToString());
var IdentityPricipal = await principalFactory.CreateAsync(User);
var IdServerPrincipal = IdentityServerPrincipal.Create(User.Id.ToString(), User.UserName, IdentityPricipal.Claims.ToArray());
Request.Subject = IdServerPrincipal;
Request.IncludeAllIdentityClaims = true;
Request.ValidatedRequest = new ValidatedRequest();
Request.ValidatedRequest.Subject = Request.Subject;
Request.ValidatedRequest.SetClient(Config.GetClients().First());
Request.Resources = new Resources(Config.GetIdentityResources(), Config.GetApiResources());
Request.ValidatedRequest.Options = options;
Request.ValidatedRequest.ClientClaims = IdServerPrincipal.Claims.ToArray();
var Token = await TS.CreateAccessTokenAsync(Request);
Token.Issuer = "http://" + HttpContext.Request.Host.Value;
var TokenValue = await TS.CreateSecurityTokenAsync(Token);
return Ok(TokenValue);
}
For a newly released IdentityServer 2.0.0 the code needs some modifications:
[HttpPost("loginas/{id}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> LoginAs(int id, [FromServices] ITokenService TS,
[FromServices] IUserClaimsPrincipalFactory<ApplicationUser> principalFactory,
[FromServices] IdentityServerOptions options)
{
var Request = new TokenCreationRequest();
var User = await userManager.FindByIdAsync(id.ToString());
var IdentityPricipal = await principalFactory.CreateAsync(User);
var IdentityUser = new IdentityServerUser(User.Id.ToString());
IdentityUser.AdditionalClaims = IdentityPricipal.Claims.ToArray();
IdentityUser.DisplayName = User.UserName;
IdentityUser.AuthenticationTime = System.DateTime.UtcNow;
IdentityUser.IdentityProvider = IdentityServerConstants.LocalIdentityProvider;
Request.Subject = IdentityUser.CreatePrincipal();
Request.IncludeAllIdentityClaims = true;
Request.ValidatedRequest = new ValidatedRequest();
Request.ValidatedRequest.Subject = Request.Subject;
Request.ValidatedRequest.SetClient(Config.GetClients().First());
Request.Resources = new Resources(Config.GetIdentityResources(), Config.GetApiResources());
Request.ValidatedRequest.Options = options;
Request.ValidatedRequest.ClientClaims = IdentityUser.AdditionalClaims;
var Token = await TS.CreateAccessTokenAsync(Request);
Token.Issuer = HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value;
var TokenValue = await TS.CreateSecurityTokenAsync(Token);
return Ok(TokenValue);
}
Use this:
http://docs.identityserver.io/en/latest/topics/tools.html
Use this tool that come with identity server:
Declare it in the constructor, to receive by dependecy injection.
IdentityServer4.IdentityServerTools _identityServerTools
var issuer = "http://" + httpRequest.Host.Value;
var token = await _identityServerTools.IssueJwtAsync(
30000,
issuer,
new System.Security.Claims.Claim[1]
{
new System.Security.Claims.Claim("cpf", cpf)
}
);
Here is another way to achieve this:
first create a custom grant named loginBy
public class LoginByGrant : ICustomGrantValidator
{
private readonly ApplicationUserManager _userManager;
public string GrantType => "loginBy";
public LoginByGrant(ApplicationUserManager userManager)
{
_userManager = userManager;
}
public async Task<CustomGrantValidationResult> ValidateAsync(ValidatedTokenRequest request)
{
var userId = Guid.Parse(request.Raw.Get("user_id"));
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
return await Task.FromResult<CustomGrantValidationResult>(new CustomGrantValidationResult("user not exist"));
var userClaims = await _userManager.GetClaimsAsync(user.Id);
return
await Task.FromResult<CustomGrantValidationResult>(new CustomGrantValidationResult(user.Id.ToString(), "custom", userClaims));
}
}
then add this custom grant in identity startup class
factory.CustomGrantValidators.Add(
new Registration<ICustomGrantValidator>(resolver => new LoginByGrant(ApplicaionUserManager)));
and finally in your api
public async Task<IHttpActionResult> LoginBy(Guid userId)
{
var tokenClient = new TokenClient(Constants.TokenEndPoint, Constants.ClientId, Constants.Secret);
var payload = new { user_id = userId.ToString() };
var result = await tokenClient.RequestCustomGrantAsync("loginBy", "customScope", payload);
if (result.IsError)
return Ok(result.Json);
return Ok(new { access_token = result.AccessToken, expires_in = result.ExpiresIn});
}
Further to my comment on your original question. Implement an impersonation feature within the implicit/hybrid flow. If a user is determined to be a "super admin" then present them with an additional step after authentication that lets them enter/select the account they wish to impersonate. Once that's done simply establish the session on the identity server as the selected user (and possibly store additional claims denoting that it is an impersonated session and who is doing the impersonation). Any tokens will then be issued as if you were that user and all without having to know the password.
Additionally if you wish to create tokens yourself have a look at the ITokenCreationService provided by IdSrv4. You can inject that into your own controller/service/whatever and use CreateTokenAsync(Token token) to generate a signed JWT with any claims you like.
A little late to answer.
in my case of Generating Access Token Without Password there was another identity server as an organization sso, and our implementation already used IdentityServer, so we need to get user token from second IdentityServer (after user login and redirected to our app), extract sub, check if it is already existed(if not insert into our local IdentityServer), finally select user and use newly grant to get token for user.
your client should have this granttype as Allowed Grant types (here userexchange):
see: identity server docs, or duende docs for more information
public class TokenExchangeGrantValidator : IExtensionGrantValidator {
protected readonly UserManager<ToranjApplicationUser> _userManager;
private readonly IEventService _events;
public TokenExchangeGrantValidator(ITokenValidator validator, IHttpContextAccessor httpContextAccessor, UserManager<ToranjApplicationUser> userManager
, IEventService events) {
_userManager = userManager;
_events = events;
}
public async Task ValidateAsync(ExtensionGrantValidationContext context) {
var userName = context.Request.Raw.Get("uname");
if (string.IsNullOrEmpty(userName)) {
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
var user = await _userManager.FindByNameAsync(userName);
// or use this one, if you are sending userId
//var user = await _userManager.FindByIdAsync(userId);
if (null == user) {
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName, false, context.Request.ClientId));
var customResponse = new Dictionary<string, object>
{
{OidcConstants.TokenResponse.IssuedTokenType, OidcConstants.TokenTypeIdentifiers.AccessToken}
};
context.Result = new GrantValidationResult(
subject: user.Id.ToString(),
authenticationMethod: GrantType,
customResponse: customResponse);
}
public string GrantType => "userexchange";
}
in your startup's ConfigureServices after var builder = services.AddIdentityServer(...) add your newly created class.
builder.AddExtensionGrantValidator<TokenExchangeGrantValidator>();
calling it to get token is as simple as:
POST /connect/token
grant_type=userexchange&
scope=yourapi&
uname=yourusername&
client_id=yourClientId
client_secret=secret

Categories