JSON Web Token - Seguridad en servicios Web API 2 de ASP.NET

En el anterior artículo de este Blog JSON Web Token - Seguridad en servicios Web API de .NET Core, vimos cómo crear un servicio Web API RESTful de ASP.NET Core con autenticación de usuarios basada en JSON Web Tokens (JWT).

Para los que sigan utilizando el Full Framework de .NET, en este nuevo artículo veremos como desarrollar el mismo ejemplo anterior,  pero esta vez utilizando ASP.NET Framework 4.6.x para crear un Web API 2 REST con seguridad JWT. Para finalizar, explicaremos con detalle,  el proceso de autenticación de los usuarios en el servicio con el gestor de peticiones HTTP Postman.

JWT

 

Creando nuestro Web API REST con Visual Studio

Para este ejemplo, utilizaremos Visual Studio 2017 con todas las actualizaciones necesarias para usar el SDK de .NET Framework 4.6.x.

En primer lugar, crearemos un nuevo proyecto del tipo Aplicación Web ASP.NET (.NET Framework), y seleccionaremos la plantilla Web API.

webapi2-prj

El Modelo de datos

Antes de crear nuestro Web API REST, debemos definir el Modelo de datos sobre el cual trabajaremos posteriormente. 

Para esto, crearemos una nueva carpeta en el proyecto llamada Models, y en su interior definiremos una nueva clase llamada Pais.cs de la siguiente manera:

    public class Pais
    {
        public Pais()
        {
            this.Id = Guid.NewGuid();
        }

        [Key]
        public Guid Id { get; set; }

        [Required]
        [StringLength(50, MinimumLength = 3)]
        public string Nombre { get; set; }

        [Required]
        public int Habitantes { get; set; }
    }

El Controlador de Web API 2

En este momento, ya podremos crear la clase controladora para nuestro Web API REST. 

Pera esto, nos situaremos en la carpeta Controllers del proyecto, y con el botón derecho del ratón accederemos al menú contextual (opción Agregar > Controlador ...) para crear un nuevo Controlador del tipo Controlador de Web API 2 con acciones que usan Entity Framework.

webapi2-controller

Seleccionando esta plantilla, Visual Studio creará por nosotros (Scaffolding) un Controlador de Web API REST con Acciones CRUD que usan Entity Framework.

Lo interesante de esta opción, es que simplemente indicándole un Modelo de datos y el nombre de la clase que actuará como Contexto de datos, Visual Studio instalará por nosotros los paquetes NuGet de Entity Framework necesarios para la aplicación, y creará un contexto de datos y cadena de conexión hacia una base de batos SQL Server del tipo (localdb)\\mssqllocaldb que posteriormente crearemos desde Entity Famework (Code First).

scaffolding-webapi

Nota: Siempre que utilicemos Entity Framework como ORM para nuestra base de datos, es recomendable marcar la opción "Usar acciones de controlador asíncrono".

Si todo ha ido bien, en este punto ya tendremos creado nuestro Controlador de Web API 2 (PaisController.cs) con Acciones CRUD (asíncronas) sobre el Modelo de datos Pais.cs

    public class PaisController : ApiController
    {
        private ApplicationDbContext db = new ApplicationDbContext();

        // GET: api/Pais
        public IQueryable<Pais> GetPais()
        {
            return db.Pais;
        }

        // GET: api/Pais/5
        [ResponseType(typeof(Pais))]
        public async Task<IHttpActionResult> GetPais(Guid id)
        {
            Pais pais = await db.Pais.FindAsync(id);
            if (pais == null)
            {
                return NotFound();
            }

            return Ok(pais);
        }

        // PUT: api/Pais/5
        [ResponseType(typeof(void))]
        public async Task<IHttpActionResult> PutPais(Guid id, Pais pais)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != pais.Id)
            {
                return BadRequest();
            }

            db.Entry(pais).State = EntityState.Modified;

            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!PaisExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return StatusCode(HttpStatusCode.NoContent);
        }

        // POST: api/Pais
        [ResponseType(typeof(Pais))]
        public async Task<IHttpActionResult> PostPais(Pais pais)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.Pais.Add(pais);

            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateException)
            {
                if (PaisExists(pais.Id))
                {
                    return Conflict();
                }
                else
                {
                    throw;
                }
            }

            return CreatedAtRoute("DefaultApi", new { id = pais.Id }, pais);
        }

        // DELETE: api/Pais/5
        [ResponseType(typeof(Pais))]
        public async Task<IHttpActionResult> DeletePais(Guid id)
        {
            Pais pais = await db.Pais.FindAsync(id);
            if (pais == null)
            {
                return NotFound();
            }

            db.Pais.Remove(pais);
            await db.SaveChangesAsync();

            return Ok(pais);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }

        private bool PaisExists(Guid id)
        {
            return db.Pais.Count(e => e.Id == id) > 0;
        }
    }

Así como nuestro Contexto de datos de Entity Framework ApplicationDbContext.cs.

    public class ApplicationDbContext : DbContext
    {            
        public ApplicationDbContext() : base("name=ApplicationDbContext")
        {
        }

        public DbSet<Pais> Pais { get; set; }
    }

Entity Framework y la Base de Datos

Durante el proceso de creación del Controlador de Web API PaisController.csVisual Studio ha generado por nosotros una cadena de conexión en el archivo de configuración Web.config con la siguiente estructura:

  <connectionStrings>
    <add name="ApplicationDbContext" 
         connectionString="Data Source=(localdb)\MSSQLLocalDB; 
                            Initial Catalog=ApplicationDbContext-20190609183807; 
                            Integrated Security=True; 
                            MultipleActiveResultSets=True; 
                            AttachDbFilename=|DataDirectory|ApplicationDbContext-20190609183807.mdf"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

Ahora ya podemos crear nuestra base de datos, y esto lo haremos a través de migraciones de Entity Framework (Code First).

En primer lugar abriremos la consola de administración de paquetes NuGet (Herramientas > Administrador de paquetes NuGet > Consola del Administrador de paquetes), y habilitaremos las migraciones de la siguiente manera:

PM> Enable-Migrations

Una vez ejecutado este comando, se habrá creado en el directorio raíz de nuestro proyecto una nueva carpeta llamada Migrations, la cual contendrá un archivo de configuración llamado Configuration.cs.

migrations

Antes de crear nuestra base de datos y comenzar a implementar la seguridad JWT, crearemos una serie de datos de prueba que serán insertados en la tabla de países a la hora de crear la base de datos.

Para ello, sobre-escribiremos el método Seed() de la clase de configuración Configuration.cs, de la siguiente manera:

    internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(ApplicationDbContext context)
        {
            //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
            //  to avoid creating duplicate seed data. E.g.
            //
            //    context.People.AddOrUpdate(
            //      p => p.FullName,
            //      new Person { FullName = "Andrew Peters" },
            //      new Person { FullName = "Brice Lambson" },
            //      new Person { FullName = "Rowan Miller" }
            //    );
            //

            context.Pais.AddOrUpdate(x => x.Id,
                new Pais { Nombre = "España", Habitantes = 46000000 },
                new Pais { Nombre = "Alemania", Habitantes = 83000000 },
                new Pais { Nombre = "Francia", Habitantes = 65000000 },
                new Pais { Nombre = "Italia", Habitantes = 61000000 }
            );
        }
    }

Como vemos, hemos añadido cuatro nuevos objetos del tipo Pais.cs, que se insertarán en la base de datos a la hora de crearla.

Ahora, ya podemos crear la migración inicial (la que creará la base de datos):

PM> Add-Migration MigracionInicial

Y por último, aplicaremos los cambios:

PM> Update-Database

Si todo ha ido bien, ya tendremos creada la base de datos y la tabla de países con los datos de prueba.

Para probar el correcto funcionamiento del Web API, podemos realizar una petición GET desde nuestro explorador (/api/pais), obteniendo el siguiente resultado:

webapi-result

Tipos de datos en respuestas HTTP

Los servicios Web API 2 de ASP.NET, están configurados por defecto para devolver respuestas en formato XML o Json indistintamente. Como vimos en el ejemplo anterior, la respuesta a una petición GET desde el explorador Chrome, nos retorna una estructura XML con los datos solicitados.

En el caso de que queramos "forzar" al Web API a devolver solo respuestas en formato Json, debemos configurar los formateadores de datos en el archivo App_Start/WebApiConfig.cs de la siguiente manera:

...
// ELIMINAMOS EL FORMATEADOR DE RESPUESTAS XML
config.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
// HABILITAMOS EL FORMATEADOR DE RESPUESTAS JSON
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
...

Envío de datos en peticiones HTTP

En cuanto al envío de datos a un Web API 2 de ASP.NET (POST y PUT), el Framework está configurado por defecto para aceptar peticiones en formato Json.

En el caso de querer enviar datos en formato XML, debemos indicarlo en el archivo de configuración WebApiConfig.cs de la siguiente manera:

...
// HABILITAMOS EL SERIALIZADOR DE XML
config.Formatters.XmlFormatter.UseXmlSerializer = true;
...

 

Configurando la seguridad con JWT

En primer lugar, instalaremos vía NuGet las librerías necesarias para poder utilizar JWT en nuestra aplicación.

Para esto abriremos la consola de administración de paquetes NuGet de Visual Studio (Herramientas > Administrador de paquetes NuGet > Consola del Administrador de paquetes), y ejecutamos el siguiente comando:

PM> Install-Package System.IdentityModel.Tokens.Jwt

 

Antes de comenzar a configurar JWT, debemos proteger nuestro Web API de accesos no autorizados.

Para ello, "decoraremos" el Controlador con el atributo [Authorize]. Esto hará que de momento, las peticiones dirigidas a los elementos afectados (Acciones) retornen un HTTP 401 Unauthorized (acceso no autorizado).

    [Authorize]
    public class PaisController : ApiController
    {
       ...
       ...
    }

Los parámetros de configuración JWT

En el archivo de configuración Web.config, definiremos una serie de parámetros básicos que posteriormente serán utilizados para generar nuestro Token. 

Issuer: Debe indicar quien es un emisor válido para el Token. Normalmente indicaremos el Dominio desde el cual se emite el Token.

Audience: Debe indicar la audiencia o destinatario a los que se dirige el Token. En nuestro caso indicaremos la URL de nuestro Web API.

ClaveSecreta: Obviamente es el parámetro de configuración más importante, ya que será la Clave que utilizaremos tanto para firmar digitalmente el Token al enviarlo, como para comprobar la validez de la firma al recibirlo.

  <appSettings>
    
    <!-- VARIABLES DE CONFIGURACIÓN JWT -->
    <add key="ClaveSecreta" value="OLAh6Yh5KwNFvOqgltw7" />
    <add key="Issuer" value="www.rafaelacosta.net" />
    <add key="Audience" value="www.rafaelacosta.net/api/miwebapi" />
    <add key="Expires" value="24" /> <!-- En Horas -->

  </appSettings>

 

Generando el Token de autenticación JWT

Llegados a este punto, ya podemos comenzar a implementar el sistema de generación de Tokens JWT, pero antes, crearemos una clase de Modelo llamada UsuarioInfo.cs, la cual representará la información que identificará de manera única a los usuarios autenticados. Esta clase la utilizaremos posteriormente para construir el Payload de nuestros Tokens.

public class UsuarioInfo
    {
        public Guid Id { get; set; }
        public string Nombre { get; set; }
        public string Apellidos { get; set; }
        public string Email { get; set; }
        public string Rol { get; set; }
    }

También necesitamos crear una clase de Modelo UsuarioLogin.cs, que represente las credenciales de acceso de un usuario determinado. En nuestro caso definiremos las propiedades Usuario y Password.

    public class UsuarioLogin
    {
        public string Usuario { get; set; }
        public string Password { get; set; }
    }

Por último crearemos un nuevo Controlador de Web API 2 (LoginController.cs), cuyo cometido será identificar mediante usuario y contraseña (UsuarioLogin.cs) a los potenciales clientes de nuestro Web API, además de generar un Token válido que posteriormente será enviado de vuelta a quien lo solicitó.

    public class LoginController : ApiController
    {

        // POST: api/Login
        [HttpPost]
        [AllowAnonymous]
        public async Task<IHttpActionResult> LoginAsync(UsuarioLogin usuarioLogin)
        {
            if (usuarioLogin == null)
                return BadRequest("Usuario y Contraseña requeridos.");

            var _userInfo = await AutenticarUsuarioAsync(usuarioLogin.Usuario, usuarioLogin.Password);
            if (_userInfo != null)
            {
                return Ok(new { token = GenerarTokenJWT(_userInfo) });
            }
            else
            {
                return Unauthorized();
            }
        }

        // COMPROBAMOS SI EL USUARIO EXISTE EN LA BASE DE DATOS 
        private async Task<UsuarioInfo> AutenticarUsuarioAsync(string usuario, string password)
        {
            // AQUÍ LA LÓGICA DE AUTENTICACIÓN //

            // Supondremos que el usuario existe en la Base de Datos.
            // Retornamos un objeto del tipo UsuarioInfo, con toda
            // la información del usuario necesaria para el Token.
            return new UsuarioInfo()
            {
                // Id del Usuario en el Sistema de Información (BD)
                Id = new Guid("B5D233F0-6EC2-4950-8CD7-F44D16EC878F"),
                Nombre = "Nombre Usuario",
                Apellidos = "Apellidos Usuario",
                Email = "email.usuario@dominio.com",
                Rol = "Administrador"
            };

            // Supondremos que el usuario NO existe en la Base de Datos.
            // Retornamos NULL.
            //return null;
        }

        // GENERAMOS EL TOKEN CON LA INFORMACIÓN DEL USUARIO
        private string GenerarTokenJWT(UsuarioInfo usuarioInfo)
        {
            // RECUPERAMOS LAS VARIABLES DE CONFIGURACIÓN
            var _ClaveSecreta = ConfigurationManager.AppSettings["ClaveSecreta"];
            var _Issuer = ConfigurationManager.AppSettings["Issuer"];
            var _Audience = ConfigurationManager.AppSettings["Audience"];
            if (!Int32.TryParse(ConfigurationManager.AppSettings["Expires"], out int _Expires))                
                _Expires = 24;


            // CREAMOS EL HEADER //
            var _symmetricSecurityKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(_ClaveSecreta));
            var _signingCredentials = new SigningCredentials(
                    _symmetricSecurityKey, SecurityAlgorithms.HmacSha256
                );
            var _Header = new JwtHeader(_signingCredentials);

            // CREAMOS LOS CLAIMS //
            var _Claims = new[] {
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.NameId, usuarioInfo.Id.ToString()),
                new Claim("nombre", usuarioInfo.Nombre),
                new Claim("apellidos", usuarioInfo.Apellidos),
                new Claim(JwtRegisteredClaimNames.Email, usuarioInfo.Email),
                new Claim(ClaimTypes.Role, usuarioInfo.Rol)
            };

            // CREAMOS EL PAYLOAD //
            var _Payload = new JwtPayload(
                    issuer: _Issuer,
                    audience: _Audience,
                    claims: _Claims,
                    notBefore: DateTime.UtcNow,
                    // Exipra a la 24 horas.
                    expires: DateTime.UtcNow.AddHours(_Expires)
                );

            // GENERAMOS EL TOKEN //
            var _Token = new JwtSecurityToken(
                    _Header,
                    _Payload
                );

            return new JwtSecurityTokenHandler().WriteToken(_Token);
        }
    }

Una vez que el usuario haya recibido el Token de acceso, ya podrá acceder con total seguridad a las Acciones de nuestro Controlador de Web API PaisController.cs protegido con el atributo [Authorize].

Este Token recibido, nos identificará como usuario autenticado en las sucesivas peticiones que realicemos al servicio Web API, sin necesidad de volvernos a validar en el sistema de información. Simplemente debemos indicar en la cabecera (Header) de las peticiones HTTP al Web API, un encabezado Authorization: del tipo bearer con el valor del Token obtenido.

 

Cómo validar el Token de autenticación JWT

En este punto del desarrollo, solo nos quedaría comprobar que el Token enviado al Web API en la cabecera de la petición HTTP, es un Token válido para nuestro sistema de autenticación. Esto quiere decir que, el Token fue firmado digitalmente con nuestra clave secreta y aún no ha caducado.

Para ello, crearemos un Handler personalizado (ValidarTokenHandler.cs) que se encargue de interceptar las peticiones HTTP a nuestro Web API de la siguiente manera: 

    internal class ValidarTokenHandler : DelegatingHandler
    {        
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, 
                                                               CancellationToken cancellationToken)
        {
            HttpStatusCode statusCode;
            string token;
            
            if (!TryRetrieveToken(request, out token))
            {
                statusCode = HttpStatusCode.Unauthorized;
                return base.SendAsync(request, cancellationToken);
            }

            try
            {
                var claveSecreta = ConfigurationManager.AppSettings["ClaveSecreta"];
                var issuerToken = ConfigurationManager.AppSettings["Issuer"];
                var audienceToken = ConfigurationManager.AppSettings["Audience"];
                
                var securityKey = new SymmetricSecurityKey(
                    System.Text.Encoding.Default.GetBytes(claveSecreta));

                SecurityToken securityToken;
                var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
                TokenValidationParameters validationParameters = new TokenValidationParameters()
                {
                    ValidAudience = audienceToken,
                    ValidIssuer = issuerToken,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    // DELEGADO PERSONALIZADO PERA COMPROBAR
                    // LA CADUCIDAD EL TOKEN.
                    LifetimeValidator = this.LifetimeValidator,
                    IssuerSigningKey = securityKey
                };

                // COMPRUEBA LA VALIDEZ DEL TOKEN
                Thread.CurrentPrincipal = tokenHandler.ValidateToken(token, 
                                                                     validationParameters, 
                                                                     out securityToken);
                HttpContext.Current.User = tokenHandler.ValidateToken(token, 
                                                                      validationParameters, 
                                                                      out securityToken);

                return base.SendAsync(request, cancellationToken);
            }
            catch (SecurityTokenValidationException ex)
            {
                statusCode = HttpStatusCode.Unauthorized;
            }
            catch (Exception ex)
            {
                statusCode = HttpStatusCode.InternalServerError;
            }

            return Task<HttpResponseMessage>.Factory.StartNew(() => 
                        new HttpResponseMessage(statusCode) { });
        }

        // RECUPERA EL TOKEN DE LA PETICIÓN
        private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
        {
            token = null;
            IEnumerable<string> authzHeaders;
            if (!request.Headers.TryGetValues("Authorization", out authzHeaders) ||
                                              authzHeaders.Count() > 1)
            {
                return false;
            }
            var bearerToken = authzHeaders.ElementAt(0);
            token = bearerToken.StartsWith("Bearer ") ?
                    bearerToken.Substring(7) : bearerToken;
            return true;
        }

        // COMPRUEBA LA CADUCIDAD DEL TOKEN
        public bool LifetimeValidator(DateTime? notBefore, 
                                      DateTime? expires, 
                                      SecurityToken securityToken, 
                                      TokenValidationParameters validationParameters)
        {
            var valid = false;

            if ((expires.HasValue && DateTime.UtcNow < expires)
                && (notBefore.HasValue && DateTime.UtcNow > notBefore))
            { valid = true; }

            return valid;
        }
    }

Como vemos en el código, la clase ValidarTokenHandler hereda de DelegatingHandler y sobreescribe su método SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) para comprobar la validez del Token recibido con la petición HTTP.

Ahora, solo faltaría añadir este Handler de validación a la cola de mensajes HTTP de nuestro Web API, en el método Register(HttpConfiguration config) del archivo de configuración App_Start/WebApiConfig.cs.

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Configuración y servicios de API web

            // Rutas de API web
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            // AÑADE EL HANDLER DE VALIDACIÓN DE TOKENS
            config.MessageHandlers.Add(new ValidarTokenHandler());
            ...
            ...
        }
    }

 

Comprobando el sistema de seguridad JWT con Postman

Una vez implementada la seguridad JWT sobre nuestro servicio Web API de ASP.NET Core, comprobaremos cómo funciona el proceso de autenticación de usuarios, utilizando la herramienta de "testeo" de APIs REST Postman.

Cómo obtener el Token de acceso

Si en este momento del desarrollo realizáramos desde Postman una petición del tipo GET: api/Pais a nuestro servicio Web API, obtendríamos una respuesta HTTP 401 Unauthorized

401

Por lo tanto, y como requisito inicial, debemos solicitar un Token JWT válido accediendo al recurso POST: api/Login.

Esto lo haremos enviando en el cuerpo de la petición (Body) un objeto JSON del tipo UsuarioLogin, con un usuario y contraseña válidos en nuestro sistema de información. Si todo ha funcionado correctamente, recibiremos en el cuerpo de la respuesta nuestro Token de acceso JWT.

login-token-jwt

Realizando una petición GET al Web API

En este momento ya estamos en disposición de realizar cualquier petición a nuestro servicio Web API PaisController, siempre y cuando indiquemos en la cabecera (Head) de dichas peticiones, nuestro Token de acceso JWT obtenido anteriormente.

Esto lo haremos mediante Postman de la siguiente manera: 

En primer lugar crearemos una nueva petición del tipo GET al recurso GET: api/Pais

Seguidamente, accederemos a la pestaña Authorization y seleccionaremos el tipo Bearer Token

A continuación copiaremos y pegaremos el Token de acceso en el campo indicado y pulsaremos el botón Preview Request.

authorization

 

Esto hará que en la cabecera de la petición se cree un encabezado Authorization de tipo bearer con el valor del Token de acceso.

GET /api/pais HTTP/1.1
Host: localhost:5001
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJlYzk2MzVmNS04ZTEzLTQ4YTMtOGE5NC1lYmE3NDZiMDdlZjkiLCJuYW1laWQiOiJiNWQyMzNmMC02ZWMyLTQ5NTAtOGNkNy1mNDRkMTZlYzg3OGYiLCJub21icmUiOiJOb21icmUgVXN1YXJpbyIsImFwZWxsaWRvcyI6IkFwZWxsaWRvcyBVc3VhcmlvIiwiZW1haWwiOiJlbWFpbC51c3VhcmlvQGRvbWluaW8uY29tIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5pc3RyYWRvciIsIm5iZiI6MTU1OTUxNjI0NCwiZXhwIjoxNTU5NjAyNjQ0LCJpc3MiOiJ3d3cucmFmYWVsYWNvc3RhLm5ldCIsImF1ZCI6Ind3dy5yYWZhZWxhY29zdGEubmV0L2FwaS9taXdlYmFwaSJ9.gokqwD7dcKwZUFVcvAcNc52WyUrVTTQjavrp0uBbESA

Por ultimo enviaremos la petición al servidor, y veremos como recibimos una respuesta HTTP 200 Ok, con el contenido de los registros de la base de datos en formato JSON. 

peticion-ge

Importante: Los Tokens JWT caducan, esto quiere decir que a la hora de generarlos, podemos indicar un tiempo de validez mediante el parámetro expires: de la clase JwtPayload(). Por esta razón debemos estar atentos a las respuestas HTTP 401 (acceso no autorizado) del Web API, para volver a solicitar un nuevo Token si fuera necesario.

Enviando una petición POST al Web API

En el caso de una petición POST, los pasos iniciales del proceso serían los mismos.

En primer lugar solicitaríamos el Token de acceso, y lo añadiríamos a la cabecera e la petición mediante el encabezado Authorization.

A continuación, crearíamos en el cuerpo (Body) de la petición, el nuevo objeto JSON del tipo Pais.cs que queremos enviar al servidor para ser insertado en la base de datos. 

Por último, enviaremos la petición al servidor, y veremos como recibimos una respuesta HTTP 201 Created con el nuevo objeto creado en la base de datos. 

peticion-post

 

   EtiquetasASP.NET JWT Web API Seguridad

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

  Comentarios

Rafael Acosta Administrador Rafael Acosta

@Fernando Gabriel Sosa Rozzi:

Las entidades UsuarioLogin y UsuarioInfo no están, ni tienen que estar relacionadas. No son representaciones (mapeos) de tablas de la Base de Datos.


UsuarioLogin es simplemente una clase Modelo que recibe la Acción LoginAsync(UsuarioLogin usuarioLogin) para poder validar que el usuario existe en la Base de Datos.


UsuarioInfo es también una clase Modelo que recibe la Acción GenerarTokenJWT(UsuarioInfo usuarioInfo), con la información del usuario necesaria para generar el Token de acceso.


Fernando Gabriel Sosa Rozzi Fernando Gabriel Sosa Rozzi

Como están relacionadas las entidades UsuarioLogin y UsuarioInfo?
Cómo al ingresar el campo usuario y password, debería traerme un "UsuarioInfo" si las tablas no están relacionadas entre sí.
En tal caso, es válido usar el ID de UsuarioInfo como ID en UsuarioLogin?
Arturo Arturo

Muy buen aporte!! Gracias
Josue Herrera Josue Herrera

Excelente este contenido, gracias por tomarte el tiempo de explicar.
Cuando trabajas con JWT y defines un tiempo de expiración de token, la autenticación del lado del cliente se hace manual cuando se vence el token?, o se deja quemada en código de el lado del cliente y esto en automático se mantiene autenticando a la web api para consumir los endpoints?

Espero haber sido claro.
Aitor Aitor

Buenas Raja,
¿Como hago que el token expire? Si hago un logout, me permite utilizar el token generado hasta que expira el tiempo establecido.
Un saludo
OSCAR VASQUEZ OSCAR VASQUEZ

Hola estimado muchas gracias por al aporte, pero tengo una consulta he creado un JWT en un api/auth con net core 2.2, y el mismo token de seguridad quiero pasarlo y/o validarlo en un api/report en net framework 4.7 para que pueda jalar los reportes creados, se puede hacer...?, lo he intentado pero me sale error en el ValidateToken, como hago para poder pasarte mi ejemplos y me puedas dar una mano de que estoy haciendo mal, Mucas Gracias.
ariel ariel

excelente articulo! sabes cual pueda ser el problema cuando funciona todo de forma local pero al publicarlo en un hosting con plesk panel lanza el error 403?
Pablo Torres Pablo Torres

Muy bueno el artículo, de los mejores en la red para JWT.
Roberto Roberto

Muchas gracias. Excelente post hermano
Damian Damian

Muy buen Post, Te hago una consulta,
Como debería manejar los claims? por se supone que ahí tengo información (usuario,rol) que me gustaría utilizar para no ir de nuevo a buscarla a la BD. Como la puedo recuperar desde el lado del API REST por supuesto.
Saludos
carlos ag carlos ag

Maravilloso !!
Eduardo Eduardo

@Rafael Acosta
Muchas Gracias por la información muy util a tomar en cuenta
Rafael Acosta Administrador Rafael Acosta

@Eduardo:

Te en cuenta que en el ejemplo, el tiempo de vida del Token JWT está definido en el Web.config a 24 (horas).


<add key="Expires" value="24" /> <!-- En Horas -->

Si tu cambias la propiedad del payload expires a años (24 años) DateTime.UtcNow.AddYears(_Expires), esto supera la vida máxima que ASP.NET permite para la vida útil de un Token.


Eduardo Eduardo

Ya me funciono había cambiado esta parte del código
expires: DateTime.UtcNow.AddYears(_Expires)
y después lo deje como esta en esta documentación y ya funciono.
Eduardo Eduardo

Me genera el Token pero al momento de poner el token en postman me dice que no esta autorizado.


Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto