Generate access token with IdentityServer4 without password

21,146

Solution 1

[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);
}

Solution 2

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)  
        }  
    );

Solution 3

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});
       }

Solution 4

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.

Solution 5

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
Share:
21,146

Related videos on Youtube

Roman Kolesnikov
Author by

Roman Kolesnikov

Updated on July 11, 2022

Comments

  • Roman Kolesnikov
    Roman Kolesnikov almost 2 years

    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?

    • Mashton
      Mashton almost 7 years
      What do you mean 'without a password'? The Resource Owner Password Crediental flow requires the client to provide the user's username and password in order to be given an access token.
    • Roman Kolesnikov
      Roman Kolesnikov almost 7 years
      Yeah! But I want to generate token on the server, not on client
    • Mashton
      Mashton almost 7 years
      Do you mean generate a token for the server, not the client? What are you trying to achieve? How will your client authenticate itself to your server?
    • Roman Kolesnikov
      Roman Kolesnikov almost 7 years
      I want to make alternative to /connect/token endpoint so admin users can generate access_token of other users without knowledge of password
    • Mashton
      Mashton almost 7 years
      An access_token is a credential that allows a client to access a protected resource: what is the client application in your scenario? Why would you want an admin to create one? Why would you want the admin to create one for a user? Why can't the client create its own? If you are not using passwords then ROPC is not the grant/flow you should be using. Either you're not explaining yourself very well (possible, because you've only written 4 sentences for this whole question) or you need to go back to basics and read up more on what identity server is, what it does and what it is for.
    • mackie
      mackie almost 7 years
      Sounds like you want an impersonation feature. We've implemented such a thing but via the implicit/hybrid flow where it is just another step in the sign in process. Permissions around impersonation are stored in the idsrv4 database. It's also easy enough to generate a signed JWT with any claims you like via a custom API but I'd recommend avoiding using anything other than implicit or hybrid for end user authentication. We only use resource owner password for legacy client support.
    • Roman Kolesnikov
      Roman Kolesnikov almost 7 years
      My client is SPA. I want to allow for superadmins to login as users and look at their problems and see the site problems by their eyes
  • Roman Kolesnikov
    Roman Kolesnikov almost 7 years
    Thanks! I have already seen ITokenCreationService , can you give me an example on how to use?
  • mackie
    mackie almost 7 years
    It only has one method that takes a IdentityServer4.Models.Token object and returns the encoded string.
  • Roman Kolesnikov
    Roman Kolesnikov almost 7 years
    Actually I suspect I need to use TokenCreationRequest instead and I don't know how to get ClaimsPrincipal from IdentityUser
  • Steve Guidi
    Steve Guidi over 6 years
    Could you please provide some context to this code? Where does it live -- in the IdentityServer host, or an external application? Looks like you're using ASPNET Core Identity for your user store too.
  • Roman Kolesnikov
    Roman Kolesnikov over 6 years
    I have an api and IdentityServer in the same app and LoginAs is part of one of its controllers. If I split api and auth parts then LoginAs will be in auth part
  • DaImTo
    DaImTo about 6 years
    Mind posting your startup i am having issues with the [FromServices] on this
  • Roman Kolesnikov
    Roman Kolesnikov about 6 years
    My stratup is very big, let me know what dependency you can't resolve?
  • Roman Kolesnikov
    Roman Kolesnikov about 5 years
    Good idea! But it's slightly limited: it can not generate RefreshToken and one needs manually generate claims, from user and its roles
  • Martin Staufcik
    Martin Staufcik about 5 years
    What would be an option for authorization of this request, if the auth part is split from the api part?
  • Roman Kolesnikov
    Roman Kolesnikov almost 5 years
    Does it work with IdentityServer4 or it is for IdentityServer3 only?
  • sajjad kalantari
    sajjad kalantari almost 5 years
    identityServer4 also support ICustomGrantValidator so it should be pretty similar
  • Mykhailo K.
    Mykhailo K. almost 4 years
    Hi, @xray. Thank you for this approach. However, would like to ask if you ever faced an error "unsupported_grant_type" on localhost?
  • Alexander
    Alexander over 3 years
    @xray it does not look like IdentityServer4 supports ICustomGrantValidator: docs.identityserver.io/en/dev/…, I think in IdentityServer4 you have to use IExtensionGrantValidator
  • Umair
    Umair over 3 years
    Is there a way to generate refresh token as well to renew the access token?
  • sajjad kalantari
    sajjad kalantari over 3 years
    @Umair the result has another fields named "RefreshToken" so u can return it with access token (refresh_token = result.RefreshToken)
  • Umair
    Umair over 3 years
    @xray perfect, will try it tonight.
  • Mokarom
    Mokarom over 3 years
    For those who are looking to generate refresh_token as well, you can try with IRefreshTokenService. As a extension to the answer by @Rem, the following piece of code will generate the refresh_token var refreshToken = await refreshTokenService.CreateRefreshTokenAsync(Request.Subject, Token, Config.GetClients().First());
  • liang
    liang over 3 years
    How to validate and decode this token manually?
  • SzilardD
    SzilardD about 2 years
    an additional step which is not in the answer is to make sure that the client has the grant type allowed: userexchange, otherwise it won't call the TokenExchangeGrantValidator