1
votes

I've searched all over on requesting a JWT / Access Bearer Token using the password grant using IdentityServer4 in asp.net core, but I cant seem to find the right way to do it.

Below is the POST Request from which I register my user.

http://localhost:52718/account/register

Below is the Bearer Token GET Request from which I can get JWT Token using IdentityServer4

http://localhost:52718/connect/token

Below is the POST Request from which I Login my user

http://localhost:52718/account/signin

Now, what I'm trying to do is when I login my user then I want a JWT / Bearer Token same as I get from here http://localhost:52718/connect/token. When I hit this URL.

enter image description here

Here is my AccountController Code:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Server.Models;
using Server.Models.AccountViewModels;
using Server.Models.UserViewModels;

namespace Server.Controllers
{
    public class AccountController : Controller
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;

        public AccountController(
            UserManager<ApplicationUser> userManager,
            RoleManager<IdentityRole> roleManager
            )
        {
            _userManager = userManager;
            _roleManager = roleManager;
        }

        [HttpPost]
        public async Task<IActionResult> Register([FromBody]RegisterViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var user = new ApplicationUser { UserName = model.UserName, FirstName = model.FirstName, LastName = model.LastName, Email = model.Email };

            var result = await _userManager.CreateAsync(user, model.Password);

            string role = "Basic User";

            if (result.Succeeded)
            {
                if (await _roleManager.FindByNameAsync(role) == null)
                {
                    await _roleManager.CreateAsync(new IdentityRole(role));
                }
                await _userManager.AddToRoleAsync(user, role);
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("userName", user.UserName));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("firstName", user.FirstName));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("lastName", user.LastName));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("email", user.Email));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("role", role));

                return Ok(new ProfileViewModel(user));
            }

            return BadRequest(result.Errors);


        }

        public async Task<IActionResult> Signin([FromBody]LoginViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var result = await _userManager.FindByNameAsync(model.UserName);

            if (result != null && await _userManager.CheckPasswordAsync(result, model.Password))
            {
                return Ok(new ProfileViewModel(result));
            }

            return BadRequest("Invalid username or password.");
        }
    }
}

When I hit signin method I successfully get the data of user.

enter image description here

But I also need a jwt / access token when user login my app.

Now my actual question is:

What can I do in my signin method so when user login it returns me token along with other user data. I hope I briefly explain my question.

Thanks

1

1 Answers

2
votes

I've found my own question answer. Before starting I show you my that code where I'm Defining the client.

public static IEnumerable<Client> GetClients()
{
        // client credentials client
    return new List<Client>
    {

        // resource owner password grant client
        new Client
        {
            ClientId = "ro.angular",
            AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },
            AllowedScopes = {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.Email,
                IdentityServerConstants.StandardScopes.Address,
                "api1"
            }
        }
    };
}

Now what I do in my Signin Method is to use the TokenClient class to request the token. To create an instance you need to pass in the token endpoint address, client id and secret.

Next I'm using Requesting a token using the password grant to allows a client to send username and password to the token service and get an access token back that represents that user.

Here is my Signin Code which I need to modify:

public async Task<IActionResult> Signin([FromBody]LoginViewModel model)
{
    var disco = await DiscoveryClient.GetAsync("http://localhost:52718");
    if (disco.IsError)
    {
        return BadRequest(disco.Error);
    }

    var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.angular", "secret");
    var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync(model.UserName, model.Password, "api1 openid");

    if (tokenResponse.IsError)
    {
        return BadRequest(tokenResponse.Error);
    }

    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var user = _userManager.FindByNameAsync(model.UserName);

    var result = await _userManager.FindByNameAsync(model.UserName);

    if (result != null && await _userManager.CheckPasswordAsync(result, model.Password))
    {
        return Ok(new ProfileViewModel(result, tokenResponse));
    }

        return BadRequest("Invalid username or password.");
}

Also I modify ProfileViewModel Class and add two new Token & Expiry:

public class ProfileViewModel
{
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }

    public string Token { get; set; }

    public int Expiry { get; set; }

    public ProfileViewModel()
    {

    }

    public ProfileViewModel(ApplicationUser user, TokenResponse UToken = null)
    {
        Id = user.Id;
        FirstName = user.FirstName;
        LastName = user.LastName;
        Email = user.Email;
        Token = UToken.AccessToken;
        Expiry = UToken.ExpiresIn;
    }

    public static IEnumerable<ProfileViewModel> GetUserProfiles(IEnumerable<ApplicationUser> users)
    {
        var profiles = new List<ProfileViewModel> { };
        foreach (ApplicationUser user in users)
        {
            profiles.Add(new ProfileViewModel(user));
        }

        return profiles;
    }
}

Now Here is my desire output. Hope this answer help others.

enter image description here