Roman Kolesnikov
Roman Kolesnikov

Reputation: 12147

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?

Upvotes: 31

Views: 30287

Answers (5)

ahmad molaie
ahmad molaie

Reputation: 1540

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

Upvotes: 4

sajjad kalantari
sajjad kalantari

Reputation: 893

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

Upvotes: 6

David Madi
David Madi

Reputation: 585

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

Upvotes: 21

Roman Kolesnikov
Roman Kolesnikov

Reputation: 12147

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

Upvotes: 34

mackie
mackie

Reputation: 5264

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.

Upvotes: 3

Related Questions