Autenticación JWT para Asp.Net Api Web


Estoy tratando de soportar JWT bearer token (Json Web Token) en mi aplicación web api y me estoy perdiendo.

Veo soporte para aplicaciones .net core y OWIN.
Actualmente estoy alojando mi aplicación a través de IIS.

¿Cómo puedo lograr este módulo de autenticación en mi aplicación? ¿Hay alguna forma en que pueda usar la configuración <authentication> similar a la forma en que uso la autenticación\windows del formulario?

Author: Cuong Le, 2016-10-27

3 answers

Respondí a esta pregunta: Cómo asegurar un ASP.NET Web API Hace 4 años usando HMAC.

Ahora, muchas cosas cambiaron en seguridad, esp JWT se está volviendo popular. Aquí, trataré de explicar cómo usar JWT de la manera más simple y básica que pueda, para que no nos perdamos de jungle of OWIN, Oauth2, ASP.NET Identidad... :).

Si no conoce el token JWT, debe echar un vistazo un poco at:

Https://tools.ietf.org/html/rfc7519

Básicamente, un token JWT se parece a:

<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>

Ejemplo:

EyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ

El token JWT tiene tres secciones:

  1. Cabecera: formato JSON codificado como base64
  2. Reclamaciones: formato JSON que está codificado como base64.
  3. Signature: Creado y firmado basado en el encabezado y las notificaciones que se codifica como una base64.

Si utiliza el sitio web jwt.io con el token anterior, puede decodificar y ver el token como se muestra a continuación:

introduzca la descripción de la imagen aquí

Técnicamente, JWT utiliza firma que se firma desde encabezados y reclamaciones con algoritmo de seguridad especificado en los encabezados (ejemplo: HMACSHA256). Por lo tanto, se requiere que JWT se transfiera a través de HTTPs si almacena información confidencial en reclamaciones.

Ahora, para usar la autenticación JWT, realmente no necesita un middleware OWIN si tiene un sistema Web Api heredado. El concepto simple es cómo proporcionar token JWT y cómo validar token cuando llega la solicitud. Eso es.

De vuelta a la demo, para mantener el token JWT ligero, solo almaceno username y expiration time en JWT. Pero de esta manera, tienes que reconstruir una nueva identidad local (principal) para agregar más información como: roles.. si quiere hacer la autorización de rol. Pero, si quieres añadir más información a JWT, depende de ti, muy flexible.

En lugar de usar middleware de OWIN, simplemente puede proporcionar punto final de token JWT usando la acción del controlador:

public class TokenController : ApiController
{
    // This is naive endpoint for demo, it should use Basic authentication to provide token or POST request
    [AllowAnonymous]
    public string Get(string username, string password)
    {
        if (CheckUser(username, password))
        {
            return JwtManager.GenerateToken(username);
        }

        throw new HttpResponseException(HttpStatusCode.Unauthorized);
    }

    public bool CheckUser(string username, string password)
    {
        // should check in the database
        return true;
    }
}

Esta es una acción ingenua, en producción debe usar POST request o Basic Authentication endpoint para proporcionar token JWT.

Cómo generar el token basado en username?

Puede usar el paquete NuGet llamado System.IdentityModel.Tokens.Jwt de MS a genere el token, o incluso otro paquete si lo desea. En la demo, utilizo HMACSHA256 con SymmetricKey:

    /// <summary>
    /// Use the below code to generate symmetric Secret Key
    ///     var hmac = new HMACSHA256();
    ///     var key = Convert.ToBase64String(hmac.Key);
    /// </summary>
    private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";

    public static string GenerateToken(string username, int expireMinutes = 20)
    {
        var symmetricKey = Convert.FromBase64String(Secret);
        var tokenHandler = new JwtSecurityTokenHandler();

        var now = DateTime.UtcNow;
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
                    {
                        new Claim(ClaimTypes.Name, username)
                    }),

            Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),

            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256Signature)
        };

        var stoken = tokenHandler.CreateToken(tokenDescriptor);
        var token = tokenHandler.WriteToken(stoken);

        return token;
    }

El punto final para proporcionar el token JWT se hace, ahora, cómo validar el JWT cuando viene la solicitud, en la demo que he construido JwtAuthenticationAttribute que hereda de IAuthenticationFilter, más detalles sobre el filtro de autenticación en aquí.

Con este atributo, puede autenticar cualquier acción, simplemente coloque este atributo en esa acción.

public class ValueController : ApiController
{
    [JwtAuthentication]
    public string Get()
    {
        return "value";
    }
}

También puedes usar OWIN middleware o DelegateHander si desea validar toda la solicitud entrante para su WebAPI (no específica en Controlador o acción)

A continuación se muestra el método principal del filtro de autenticación:

    private static bool ValidateToken(string token, out string username)
    {
        username = null;

        var simplePrinciple = JwtManager.GetPrincipal(token);
        var identity = simplePrinciple.Identity as ClaimsIdentity;

        if (identity == null)
            return false;

        if (!identity.IsAuthenticated)
            return false;

        var usernameClaim = identity.FindFirst(ClaimTypes.Name);
        username = usernameClaim?.Value;

        if (string.IsNullOrEmpty(username))
            return false;

        // More validate to check whether username exists in system

        return true;
    }

    protected Task<IPrincipal> AuthenticateJwtToken(string token)
    {
        string username;

        if (ValidateToken(token, out username))
        {
            // based on username to get more information from database in order to build local identity
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, username)
                // Add more claims if needed: Roles, ...
            };

            var identity = new ClaimsIdentity(claims, "Jwt");
            IPrincipal user = new ClaimsPrincipal(identity);

            return Task.FromResult(user);
        }

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

El flujo de trabajo es, utilizando la biblioteca JWT (paquete NuGet anterior) para validar el token JWT y luego devolver ClaimsPrincipal. Puede realizar más validaciones, como verificar si el usuario existe en su sistema y agregar otras validaciones personalizadas si lo desea. El código para validar el token JWT y obtener principal volver:

   public static ClaimsPrincipal GetPrincipal(string token)
    {
        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;

            if (jwtToken == null)
                return null;

            var symmetricKey = Convert.FromBase64String(Secret);

            var validationParameters = new TokenValidationParameters()
            {
               RequireExpirationTime = true,
               ValidateIssuer = false,
               ValidateAudience = false,
               IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
            };

            SecurityToken securityToken;
            var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);

            return principal;
        }

        catch (Exception)
        {
            //should write log
            return null;
        }
    }

Si el token JWT está validado y el principal es devuelto, debe crear una nueva identidad local y poner más información en ella para verificar la autorización de roles.

Recuerde agregar config.Filters.Add(new AuthorizeAttribute()); (autorización predeterminada) en el ámbito global para evitar cualquier solicitud anónima a sus recursos.

Puedes usar Postman para probar la demo:

Token de solicitud (ingenuo como mencioné anteriormente, solo para demo):

GET http://localhost:{port}/api/token?username=cuong&password=1

Ponga el token JWT en el encabezado para solicitud autorizada, ejemplo:

GET http://localhost:{port}/api/value

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s

La demo se pone aquí: https://github.com/cuongle/WebApi.Jwt

 378
Author: Cuong Le,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-29 21:22:38

Creo que debería usar algún servidor de parte 3d para soportar el token JWT y no hay soporte JWT listo para usar en WEB API 2.

Sin embargo, hay un proyecto OWIN para soportar algún formato de token firmado (no JWT). Funciona como un protocolo OAuth reducido para proporcionar una forma simple de autenticación para un sitio web.

Puede leer más al respecto, por ejemplo, aquí.

Es bastante largo, pero la mayoría de las partes son detalles con controladores y ASP.NET Identidad que usted puede que no necesite nada. Los más importantes son

Paso 9: Agregar soporte para la generación de Tokens OAuth Bearer

Paso 12: Probando la API de Back-end

Allí puede leer cómo configurar endpoint (por ejemplo, "/token") al que puede acceder desde frontend (y detalles sobre el formato de la solicitud).

Otros pasos proporcionan detalles sobre cómo conectar ese punto final a la base de datos, etc. y usted puede elegir las piezas que usted requiere.

 1
Author: Ilya Chernomordik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-27 09:47:46

También implemento Jason Web Token API en mi proyecto, se puede descargar desde este enlace JWT API Token. Puede usar [authorize] para comprobar si un usuario está autenticado o no?

 1
Author: Brijesh Mavani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-13 05:46:41