I don't know if I'm just not looking in the right places, but I cannot seem to find the right guidance on where to begin working with React / .NET Core 2.1 Web API and (on-prem) Active Directory authentication.
I'm relatively new to .NET authentication in general, and completely new to Active Directory authentication.
I started by using the .NET Core 2.1 React template and attempting to add auth to it, but got completely lost.
Where do I even start?
For me, step one was to set up JWT authentication, such as described in this MSDN blog post.
Next, I had to find a library to use to check a user against Active Directory. I chose System.DirectoryServices.AccountManagement (available for .NET Core).
Now, I had to create a new controller with an [AllowAnonymous]
attribute. I called it LoginController
, and created an action that looked like the following:
[AllowAnonymous]
[HttpPost]
// Notice: We get a custom request object from the body
public async Task<IActionResult> Login([FromBody] AuthRequest request)
{
// Create a context that will allow you to connect to your Domain Controller
using (var adContext = new PrincipalContext(ContextType.Domain, "mydomain.com"))
{
var result = adContext.ValidateCredentials(request.username, request.password);
if (result)
{
// Create a list of claims that we will add to the token.
// This is how you can control authorization.
var claims = new[]
{
// Get the user's Name (this can be whatever claims you wish)
new Claim(ClaimTypes.Name, request.username)
};
// Read our custom key string into a a usable key object
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("SOME_TOKEN").Value));
// create some signing credentials using out key
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// create a JWT
var token = new JwtSecurityToken(
issuer: "mydomain.com",
audience: "mydomain.com",
claims: claims, // the claims listed above
expires: DateTime.Now.AddMinutes(30), // how long you wish the token to be active for
signingCredentials: creds);
Since we return an IActionResult, wrap the token inside of a status code 200 (OK)
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
}
}
}
// if we haven't returned by now, something went wrong and the user is not authorized
return Unauthorized();
}
The AuthRequest
object could look something like this:
public class AuthRequest
{
public string username { get; set; }
public string password { get; set; }
}
Now, in my React app, all I have to do is make a simple fetch request to the LoginController
with the user's username & password that I can get from a login form. The result will be a JWT I can save to state (But should save to cookies: the react-cookie
library makes that trivial).
fetch(`login`, {
method: "POST",
headers: {
'content-type': 'application/json',
'accept': 'application/json',
},
body: JSON.stringify({this.state.username, this.state.password})
}).then((response) => {
if (response.status === 401) {
// handle the 401 gracefully if this user is not authorized
}
else {
// we got a 200 and a valid token
response.json().then(({ token }) => {
// handle saving the token to state/a cookie
})
}
})
You now have the ability to add the [Authorize]
attribute to any of your controllers in your .NET Core application, and make a fetch request to it while passing your JWT from your React client, like this:
await fetch(`someController/someAction`,
{
method: 'GET'
headers: {
'content-type': 'application/json',
'authorization': `Bearer ${YOUR_JWT}`
}
})
.then(response => doSomething());
If you wanted to use this JWT with a SignalR Hub
, add the [Authorize]
attribute to your Hub
in your .NET Core project. Then, In your React client, when you instantiate the connection to your hub:
import * as signalR from '@aspnet/signalr';
var connection = new signalR.HubConnectionBuilder().withUrl('myHub', { accessTokenFactory: () => YOUR_JWT })
And, viola! A .NET Core React application capable of authorized real-time communication!