1

I have a .net core web api and SPA client in React JS. I want to allow users to login from the client with their emails registered in Azure AD, and secure my web api with JWT Token. i tried to generate tokens with a simple hardcoded username and password, but I don't have any idea how to get users from Azure AD and generate JWT Tokens.

Here is my JWTController :

[Route("api/[controller]")]
public class JwtController : Controller
{
    private readonly JwtIssuerOptions _jwtOptions;
    private readonly ILogger _logger;
    private readonly JsonSerializerSettings _serializerSettings;

    public JwtController(IOptions<JwtIssuerOptions> jwtOptions, ILoggerFactory loggerFactory)
    {
        _jwtOptions = jwtOptions.Value;
        ThrowIfInvalidOptions(_jwtOptions);

        _logger = loggerFactory.CreateLogger<JwtController>();

        _serializerSettings = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented
        };
    }

    [HttpPost]
    [AllowAnonymous]
    public async Task<IActionResult> Get([FromForm] string Username, string Password)
    {
        var applicationUser = new ApplicationUser();
        applicationUser.UserName = Username;
        applicationUser.Password = Password;
        var identity = await GetClaimsIdentity(applicationUser);
        if (identity == null)
        {
            _logger.LogInformation($"Invalid username({applicationUser.UserName}) or password ({applicationUser.Password})");
            return BadRequest("Invalid credentials");
        }

        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, applicationUser.UserName),
            new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
            new Claim(JwtRegisteredClaimNames.Iat,
                ToUnixExpochDate(_jwtOptions.IssuedAt).ToString(),
                ClaimValueTypes.Integer64),
            identity.FindFirst("Disney")
        };

        //Create the JWT security token and encode it.
        var jwt = new JwtSecurityToken(
            issuer: _jwtOptions.Issuer,
            audience: _jwtOptions.Audience,
            claims:claims,
            notBefore:_jwtOptions.NotBefore,
            expires:_jwtOptions.Expiration,
            signingCredentials:_jwtOptions.SigningCredentials);

        var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

        //Serialize and return the response.
        var response = new
        {
            access_token = encodedJwt,
            expires_in = (int)_jwtOptions.ValidFor.TotalSeconds
        };

        var json = JsonConvert.SerializeObject(response, _serializerSettings);
        return new OkObjectResult(json);
    }

    private static void ThrowIfInvalidOptions(JwtIssuerOptions options)
    {
        if (options == null) throw new ArgumentNullException(nameof(options));

        if (options.ValidFor <= TimeSpan.Zero)
        {
            throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(JwtIssuerOptions.ValidFor));
        }

        if (options.SigningCredentials == null)
        {
            throw new ArgumentNullException(nameof(JwtIssuerOptions.SigningCredentials));
        }

        if (options.JtiGenerator == null)
        {
            throw new ArgumentNullException(nameof(JwtIssuerOptions.JtiGenerator));
        }
    }

    private static long ToUnixExpochDate(DateTime date)
        => (long)Math.Round((date.ToUniversalTime() -
            new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero))
            .TotalSeconds);

    private Task<ClaimsIdentity> GetClaimsIdentity(ApplicationUser user)
    {
        if (user.UserName == "mickey" && user.Password == "mouse")
        {
            return Task.FromResult(new ClaimsIdentity(
                new GenericIdentity(user.UserName, "Token"),
                new[]
                {
                    new Claim("Disney", "mickey")
                }));
        }

        if (user.UserName == "notmickey" && user.Password == "mouse")
        {
            return Task.FromResult(new ClaimsIdentity(
                new GenericIdentity(user.UserName, "Token"),
                new Claim[] { }));
        }

        return Task.FromResult<ClaimsIdentity>(null);
    }
}

Anyone have an idea how to implement this?

1 Answer 1

3

I think you've got it a bit backwards.

Your React client should be doing a redirect to Azure AD sign-in page, and then fetching the JWT from Azure AD to call your API. Then your API simply needs to validate the incoming tokens and build a user identity for the request. There are ready-made components for that in ASP.NET Core.

One example of using Adal.js with React: https://blog.mastykarz.nl/building-office-365-web-applications-react/

Example of Azure AD v2 usage in ASP.NET MVC Core API: https://contos.io/protecting-a-net-core-api-with-azure-active-directory-59bbcd5b3429

Sign up to request clarification or add additional context in comments.

6 Comments

I don't get it. How the JWT tokens are going to be generated from AzureAD with a simple redirection in the client side?
See the flow chart here for an idea of how it works: learn.microsoft.com/en-us/azure/active-directory/develop/…. If your API and SPA front-end are effectively the same app in Azure AD, you can do as they mention there, and just send the ID token. Otherwise, you can use Adal.js to fetch additional access tokens for other APIs.
There is also an example app that uses Angular: github.com/Azure-Samples/…
I'm gonna look at this, Thank you for your help!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.