Trabajando a partir de la fabulosa respuesta de Matt Dekrey , he creado un ejemplo completamente funcional de autenticación basada en token, trabajando contra ASP.NET Core (1.0.1). Puede encontrar el código completo en este repositorio en GitHub (ramas alternativas para 1.0.0-rc1 , beta8 , beta7 ), pero en resumen, los pasos importantes son:
Genere una clave para su aplicación
En mi ejemplo, genero una clave aleatoria cada vez que se inicia la aplicación, tendrá que generar una y almacenarla en algún lugar y proporcionarla a su aplicación. Vea este archivo para saber cómo estoy generando una clave aleatoria y cómo podría importarla desde un archivo .json . Como se sugiere en los comentarios de @kspearrin, la API de protección de datos parece un candidato ideal para administrar las claves "correctamente", pero aún no he resuelto si eso es posible. ¡Envíe una solicitud de extracción si lo resuelve!
Startup.cs - ConfigureServices
Aquí, necesitamos cargar una clave privada para que se firmen nuestros tokens, que también usaremos para verificar los tokens a medida que se presentan. Estamos almacenando la clave en una variable de nivel de clase key
que reutilizaremos en el método de configuración a continuación. TokenAuthOptions es una clase simple que contiene la identidad de firma, la audiencia y el emisor que necesitaremos en TokenController para crear nuestras claves.
// Replace this with some sort of loading from config / file.
RSAParameters keyParams = RSAKeyUtils.GetRandomKey();
// Create the key, and a set of token options to record signing credentials
// using that key, along with the other parameters we will need in the
// token controlller.
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthOptions()
{
Audience = TokenAudience,
Issuer = TokenIssuer,
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.Sha256Digest)
};
// Save the token options into an instance so they're accessible to the
// controller.
services.AddSingleton<TokenAuthOptions>(tokenOptions);
// Enable the use of an [Authorize("Bearer")] attribute on methods and
// classes to protect.
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
También hemos establecido una política de autorización que nos permite usar [Authorize("Bearer")]
en los puntos finales y clases que deseamos proteger.
Startup.cs - Configurar
Aquí, necesitamos configurar JwtBearerAuthentication:
app.UseJwtBearerAuthentication(new JwtBearerOptions {
TokenValidationParameters = new TokenValidationParameters {
IssuerSigningKey = key,
ValidAudience = tokenOptions.Audience,
ValidIssuer = tokenOptions.Issuer,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew - i.e.
// provides a tolerance on the token expiry time
// when validating the lifetime. As we're creating the tokens
// locally and validating them on the same machines which
// should have synchronised time, this can be set to zero.
// Where external tokens are used, some leeway here could be
// useful.
ClockSkew = TimeSpan.FromMinutes(0)
}
});
TokenController
En el controlador de token, debe tener un método para generar claves firmadas utilizando la clave que se cargó en Startup.cs. Hemos registrado una instancia de TokenAuthOptions en Startup, por lo que debemos inyectarla en el constructor de TokenController:
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly TokenAuthOptions tokenOptions;
public TokenController(TokenAuthOptions tokenOptions)
{
this.tokenOptions = tokenOptions;
}
...
Luego, deberá generar el token en su controlador para el punto final de inicio de sesión, en mi ejemplo, estoy tomando un nombre de usuario y contraseña y validando los que usan una instrucción if, pero lo clave que debe hacer es crear o cargar un reclamo basada en identidad y generar el token para eso:
public class AuthRequest
{
public string username { get; set; }
public string password { get; set; }
}
/// <summary>
/// Request a new token for a given username/password pair.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public dynamic Post([FromBody] AuthRequest req)
{
// Obviously, at this point you need to validate the username and password against whatever system you wish.
if ((req.username == "TEST" && req.password == "TEST") || (req.username == "TEST2" && req.password == "TEST"))
{
DateTime? expires = DateTime.UtcNow.AddMinutes(2);
var token = GetToken(req.username, expires);
return new { authenticated = true, entityId = 1, token = token, tokenExpires = expires };
}
return new { authenticated = false };
}
private string GetToken(string user, DateTime? expires)
{
var handler = new JwtSecurityTokenHandler();
// Here, you should create or look up an identity for the user which is being authenticated.
// For now, just creating a simple generic identity.
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user, "TokenAuth"), new[] { new Claim("EntityID", "1", ClaimValueTypes.Integer) });
var securityToken = handler.CreateToken(new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor() {
Issuer = tokenOptions.Issuer,
Audience = tokenOptions.Audience,
SigningCredentials = tokenOptions.SigningCredentials,
Subject = identity,
Expires = expires
});
return handler.WriteToken(securityToken);
}
Y eso debería ser. Simplemente agregue [Authorize("Bearer")]
a cualquier método o clase que desee proteger, y debería obtener un error si intenta acceder sin un token presente. Si desea devolver un error 401 en lugar de 500, deberá registrar un controlador de excepción personalizado como lo he hecho en mi ejemplo aquí .