10 de enero de 2019

Consumir web api utilizando jQuery.ajax()

se consumirá un servicio web api desde MVC utilizando jQuery, ya se desarrolló el servicio y se consumió desde MVC utilizando HttpClient en esta entrada.


el servicio webApi se encuentra alojado en IIS Express puerto 55987



esta es la tabla en a que se mostraran los datos obtenidos con el verbo GET
ID Nombre Departamento Cargo
MÉTODO GET
Obtener todos los registros código WebApi
[Route("api/Empleados")]
[ResponseType(typeof(List))]
public List Getempleadoes()
{
 var Empleados = (from dato in db.empleadoes
  select new EmpleadoModelApi()
  {
  id = dato.id,
  Nombres = dato.nombre,
  Cargo = dato.Cargo.Cargo1,
  Dpto = dato.Dpto.Dpto1
  }).ToList();
 return Empleados;
}

codigo javascript, como ejemplo se ejecuta al presionar un botón
$("#boton_obtenerTodos").click(function () {
$.ajax({
 type: "GET",
 url: "http://localhost:55987/api/Empleados",
 contentType: "application/json; charset=utf-8",
 dataType: "json",
 success: function (data) {
  $('#Table > tbody').empty();
  $.each(data, function (i, item) {
  var rows = 
  "" +
  "" + item.id + "" +
  "" + item.Nombres + "" +
  "" + item.Cargo + "" +
  "" + item.Dpto + "" +
  "";
  $('#Table > tbody').append(rows);
  });
  console.log(data);
 },
 failure: function (data) {
  alert(data.responseText);
 },
 error: function (data) {
  alert(data.responseText);
 }
});
});

MÉTODO GET/{id}
Obtener un registro especifico según id código Web Api, se muestra solamente el encabezado.
[Route("api/Empleado/{id}")]
[ResponseType(typeof(empleado))]
public IHttpActionResult Getempleado(int id)

código javascript, como ejemplo se ejecuta al presionar un botón
$("#boton_buscar").click(function () {
var id = $("#busqueda").val();
$.ajax({
 type: "GET",
 url: "http://localhost:55987/api/Empleado/"+ id,
 contentType: "application/json; charset=utf-8",
 dataType: "json",
 success: function (data, textStatus, jqXHR) {
  $("#Table").empty();
   var rows = 
   "" +
   "" + data.id + "" +
   "" + data.Nombres + "" +
   "" + data.Cargo + "" +
   "" + data.Dpto + "" +
   "";
   $('#Table').append(rows);
   console.log(data);
   }      
  },
 failure: function (data) {
   alert(data.responseText);
 },
 error: function (data) {
   alert(data.responseText);
 }
 });
});

MÉTODO POST
Agregar un nuevo registro código Web Api
[Route("api/PostEmpleado")]
[ResponseType(typeof(empleado))]
public IHttpActionResult Postempleado(CrearEmpleadoApi empleado)
{
 try{
 empleado clase = new empleado();
 clase.id = Convert.ToInt32(empleado.id);
 clase.nombre = empleado.Nombres.ToString();
 clase.IdCargo = Convert.ToInt32(empleado.cargo);
 clase.IdDpto = Convert.ToInt32(empleado.Dpto);
 ...
 return Ok(empleado);
 }
 catch (DbUpdateException)
 { ... }
}

código javascript
$("#boton_ingresar").click(function () {
var idValue = 6969;
var NombresValue = "Prueba desde Web Api";
var cargoValue = 2;
var DptoValue = 2;
$.ajax({
  url: "http://localhost:55987/api/PostEmpleado",
  type: "Post",
  data: JSON.stringify({
   id: idValue,
   Nombres: NombresValue,
   cargo: cargoValue,
   Dpto: DptoValue
  }),
  contentType: 'application/json; charset=utf-8',
 success: function (data) {
  alert('Registro aregado exitosamente !!!');
 },
 failure: function (data)
 {
  alert(data.responseText);
 },
 error: function (data)
 {
  alert(data.responseText);
 }
});
});

MÉTODO DELETE
Eliminar registro especifico según id código WebApi
[Route("api/DeleteEmpleado/{id}")]
[ResponseType(typeof(empleado))]
public IHttpActionResult Deleteempleado(int id)

código javascript
$("#boton_borrar").click(function () {
var id = $("#busqueda").val();
$.ajax({
  type: "Delete",
  url: "http://localhost:55987/api/DeleteEmpleado/" + id,
  contentType: "application/json; charset=utf-8",
  dataType: "json",
 success: function (data) {
  alert('Registro Eliminado');
 },
 failure: function (data) {
  alert(data.responseText);
 },
 error: function (data) {
  alert(data.responseText);
 }
});
});


4 de enero de 2019

JWT en Web Api


JWT es un estándar RFC 7519 para transmitir información con la identidad y claims de un usuario de forma segura entre un cliente/servidor. Dicha información puede ser verificada y confiable porque está firmada digitalmente.


Así en vez de crear una sesión en el servidor y estar enviando cookies a los usuarios, se utilizará un mecanismo de autenticación sin estado (la sesión del usuario nunca se guarda en el proveedor de identidad o en el proveedor del servicio) retornando un JSON Web Token via HTTP Header siempre y cuando el usuario ingrese sus credenciales correctamente.

El usuario será ahora el responsable de guardar el token que el servidor le envia en el proceso de autenticación a fin de acceder a una ruta protegida del servicio Web Api, si el token está presente y es válido, el proveedor del servicio otorga accesos a los recursos protegidos. Como los JWTs contienen toda la información necesaria en sí misma, se reduce la necesidad de consultar base de datos u otras fuentes de información múltiples veces.

Propiedades claim estándar
El estándar define los siguientes campos que pueden incluirse en los tokens JWT:
·         Issuer (iss) - Identifica el proveedor de identidad que emitió el JWT.
·         Subject (sub) - Identifica el objeto o usuario en nombre del cual fue emitido el JWT, se puede usar para limitar su uso a ciertos casos.
·         Audience (aud) - Identifica la audiencia o receptores para lo que el JWT fue emitido. Cada servicio que recibe un JWT para su validación tiene que controlar la audiencia a la que el JWT está destinado. Si el proveedor del servicio no se encuentra presente en el campo aud, entonces el JWT tiene que ser rechazado.
·         Expiration Time (exp) - Una fecha que sirva para verificar si el JWT está vencido y obligar al usuario a volver a autenticarse.
·         Not Before (nbf) - Indica desde que momento se va a empezar a aceptar un JWT,  EL JWT no tiene que ser aceptado si el token es utilizando antes de este tiempo. 
·         Issued At (iat) - Indica cuando fue creado el JWT.
·         JWT ID (jti) - Un identificador único para cada JWT.

para el ejemplo lo primero será crear la contraseña a utilizar como secret key en el proceso de autenticación, se creó en la página https://passwordsgenerator.net/ con una longitud de 128 tal como se muestra

se generó el password +$3Vs@JmcdncM?@yBs$D_yFJ$TR5#pDbKwuX6787=k@6^GYN*x*#9hrEv9tBefaEG7e3XHh=Lw62$$hxN-g#artN8=Xqg&6Pt#W=yYU5!RT6B#VRcq4+v$L+rM4UEj25

la aplicación cliente que consumirá el servicio se encuentra en el puerto 55987 en IIS Express tal como se muestra en la figura, este numero de puerto se utilizara en los claims issuer y audience del JWT.


colocar en el controlador Empleado de la aplicación Web Api el atributo Authorize, de esta manera solamente los clientes que envíen el token podrán consumir el servicio


se utilizará la aplicación Web Api creada en esta entrada y se agregaran:
el archivo Models.cs en el dentro de la carpeta Models.
el archivo LoginController dentro de la carpeta Controllers.
el archivo TokenValidationHandler en el directorio raíz, con lo que el Solution Explorer de la aplicación queda


en el archivo Models.cs se definen las clases para login de usuario y para respuesta ante login fallido
namespace WebApi2MVC.Models
{
 public class Models
 {}
  
 public class LoginRequest{
 public string Username { get; set; }
 public string Password { get; set; }
 }

 public class LoginResponse{
 public LoginResponse()
 {
 this.Token = "";
 this.responseMsg = new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.Unauthorized };
 }
      
 public string Token { get; set; }
 public HttpResponseMessage responseMsg { get; set; }
 }
}

Ahora debe agregarse el archivo TokenValidationHandler.cs en el cual se utilizará DelegatingHandler para capturar los mensajes HTTP enviados al servicio a fin de verificar que existe un token valido.
namespace WebApi2MVC
{
internal class TokenValidationHandler : DelegatingHandler
 {
 private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
 {
  token = null;
  IEnumerable 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;
 }

 protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 {
  HttpStatusCode statusCode;
  string token;
  if (!TryRetrieveToken(request, out token))
  {
   statusCode = HttpStatusCode.Unauthorized;
   return base.SendAsync(request, cancellationToken);
  }

  try
  {
   const string sec = "+$3Vs@JmcdncM?@yBs$D_yFJ$TR5#pDbKwuX6787=k@6^GYN*x*#9hrEv9tBefaEG7e3XHh=Lw62$$hxN-g#artN8=Xqg&6Pt#W=yYU5!RT6B#VRcq4+v$L+rM4UEj25";
   var now = DateTime.UtcNow;
   var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(sec));

   SecurityToken securityToken;
   JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
   TokenValidationParameters validationParameters = new TokenValidationParameters()
   {
    ValidAudience = "http://localhost:55987",
    ValidIssuer = "http://localhost:55987",
    ValidateLifetime = true,
    ValidateIssuerSigningKey = true,
    LifetimeValidator = this.LifetimeValidator,
    IssuerSigningKey = securityKey
   };
   Thread.CurrentPrincipal = handler.ValidateToken(token, validationParameters, out securityToken);
   HttpContext.Current.User = handler.ValidateToken(token, validationParameters, out securityToken);
   return base.SendAsync(request, cancellationToken);
  }
  catch (SecurityTokenValidationException)
  {
   statusCode = HttpStatusCode.Unauthorized;
  }
  catch (Exception)
  {
   statusCode = HttpStatusCode.InternalServerError;
  }
   return Task.Factory.StartNew(() => new HttpResponseMessage(statusCode) { });
  }

  public bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
  {
   if (expires != null)
   {
    if (DateTime.UtcNow < expires) return true;
   }
  return false;
  }

 }
}
el archivo LoginController será el encargado de validar las credenciales, en caso de credenciales validas se creará el token y se enviará al cliente
namespace WebApi2MVC.Controllers
{
[RoutePrefix("api/login")]
 public class LoginController : ApiController
 {
 [AllowAnonymous]
 [HttpPost]
 [Route("authenticate")]
 public IHttpActionResult Authenticate(LoginRequest login)
 {
 var loginResponse = new LoginResponse { };
 LoginRequest loginrequest = new LoginRequest { };
 loginrequest.Username = login.Username.ToLower();
 loginrequest.Password = login.Password;

 IHttpActionResult response;
 HttpResponseMessage responseMsg = new HttpResponseMessage();
 bool isUsernamePasswordValid = false;

 if (login != null)
 isUsernamePasswordValid = loginrequest.Password == "pabletoreto.blogspot.com" ? true : false;
     
 if (isUsernamePasswordValid)
 {
 string token = createToken(loginrequest.Username);
 return Ok(token);
 }
 else
 {
 loginResponse.responseMsg.StatusCode = HttpStatusCode.Unauthorized;
 response = ResponseMessage(loginResponse.responseMsg);
 return response;
 }
 }

 private string createToken(string username)
 {
 DateTime issuedAt = DateTime.UtcNow;
 DateTime expires = DateTime.UtcNow.AddDays(7);
 var tokenHandler = new JwtSecurityTokenHandler();

 ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
 {
 new Claim(ClaimTypes.Name, username)
 ,new Claim(ClaimTypes.Country, "El Salvador")
 ,new Claim(ClaimTypes.Gender, "Macho")
 ,new Claim(ClaimTypes.Surname, "pabletoreto")
 ,new Claim(ClaimTypes.Email, "pabletoreto@mail.com")
 ,new Claim(ClaimTypes.Role, "IT")
 });

 const string sec = "+$3Vs@JmcdncM?@yBs$D_yFJ$TR5#pDbKwuX6787=k@6^GYN*x*#9hrEv9tBefaEG7e3XHh=Lw62$$hxN-g#artN8=Xqg&6Pt#W=yYU5!RT6B#VRcq4+v$L+rM4UEj25";
 var now = DateTime.UtcNow;
 var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(sec));
 var signingCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256Signature);

 var token =
 (JwtSecurityToken)tokenHandler.CreateJwtSecurityToken(
  issuer: "http://localhost:55987", 
  audience: "http://localhost:55987",
  subject: claimsIdentity,
  notBefore: issuedAt,
  expires: expires,
  signingCredentials: signingCredentials);
            
  // claims personalizados
  var jsonu = new { id = "pabletoreto el mero" };
  token.Payload["user"] = jsonu;

  var payload = new JwtPayload
  {
  { "some ", "hello "},
  { "scope", "http://dummy.com/"},
  };

  var tokenString = tokenHandler.WriteToken(token);
  return tokenString;
 }
}
}

para finalizar se debe registrar la clase TokenValidationHandler en el WebApiConfig.cs

public static class WebApiConfig
{
 public static void Register(HttpConfiguration config)
 {
 config.MapHttpAttributeRoutes();
 config.MessageHandlers.Add(new TokenValidationHandler());

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

 config.EnableCors(new EnableCorsAttribute("*", "*", "GET,PUT,POST,DELETE"));
 var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
 config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
 }
}
ahora se realizarán las pruebas desde Postman, primero se realizará el login


la respuesta es una cadena alfanumérica generada desde el servicio y es enviada al cliente para autenticaciones futuras, evitando tener que enviar credenciales en cada invocación.


esta cadena consta de tres partes separadas por un punto, para decodificar la cadena se puede utilizar la página https://jwt.io/

Ahora se realizará una peticion con el verbo GET para la cual deberemos enviar el token junto con la petición, este token se envía en el encabezamiento de Authorization utilizando el esquema Bearer, este esquema de autenticación fue creado como parte de OAuth 2.0 y en este caso implica darle acceso al portador de este token