ASP.NET: Obtener el ID del usuario actual

Buenas!

No se vosotros, pero yo cuando desarrollo mis aplicaciones, si uso FKs de la otabla de usuarios, las hago en base al ID del usuario, nunca en base a su nombre. Así pues, saber el ID del usuario actualmente autenticado en mi aplicación es algo fundamental.

Primero, para saber el nombre del usuario autenticado podemos usar:

string userName = HttpContext.Current.User.Identity.Name;

El tema está en que las mentes pensantes que parieron el sistema de proveedores de autenticación en ASP.NET no tuvieron a bien a poner un campo para guardar el ID.

Por suerte obtenerlo es trivial:

int i = (int)Membership.GetUser().ProviderUserKey;

Ojo con ese código: Yo uso un membership provider propio, ya que mi base de datos usa ints para los IDs de usuarios. Si usáis el membership provider que viene por defecto en ASP.NET, el cast lo debéis hacer a Guid y no int.

Bueno todo muy bonito, pero antes que descorchéis el cava: eso hace una llamada a la base de datos. Además se trae todos los campos del registro correspondiente de la tabla de usuarios (que si usáis el membership provider que viene por defecto tiene la hostia y pico). O sea que cuidado con usar eso a mansalva… 🙂

Eeeerrr… ¿se puede SIN necesidad de acceder a la base de datos?

Bueno… esa es la gran pregunta, no nos vamos a engañar 😉 Hay varias maneras de poder acceder al ID del usuario sin hacer un round-trip a la base de datos, pero a si a bote pronto se me ocurren dos:

  1. Guardarlo en una variable de sesión: Podemos guardar el ID en una variable de sesión y consultarla cuando la necesitamos. Para una escalabilidad máxima podéis no usar sticky sessions y en el Session_Start guardar dicha variable con el ID. Si no usáis sticky sessions un mismo usuario puede iniciar sesión en varios IIS a la vez, pero en nuestro caso no es problemático (simplemente se consultará el ID del usuario cada vez que inicie sesión). Eso sí, estoy asumiendo que no guardáis nada más en la sesión (es decir que funcionalmente no dependéis de la sesión).
  2. Guardarlo en la cookie de autenticación del usuario. Esto no es, ni de lejos tan sencillo como el punto anterior, pero ya que hemos llegado hasta aquí…

Modificar la cookie de autenticación

Primero debemos hacer que cuando se cree la cookie de autenticación se añada el ID del usuario. En mi caso, como siempre, uso ASP.NET MVC, así que modificaré el código de AccountController (que es el que genera VS). Para aplicaciones webforms ese código debe colocarse cuando se va a hacer login del usuario.

En el código por defecto que genera VS para autenticar un usuario se usa:

public void SignIn(string userName, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");

FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
}

Este código está en la clase FormsAuthenticationService (dentro de AccountsModel.cs) y no tiene ningún secreto: lo que hace es crear la cookie de autenticación de ASP.NET.

En nuestro caso vamos a modificar ese código por el siguiente:

public void SignIn(string userName, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
int id = 100; // Aquí va el ID del usuario que pillaríamos de la BBDD
string userData = id.ToString();
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(30), createPersistentCookie, userData);
string encTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
HttpContext.Current.Response.Cookies.Add(faCookie);
}

Lo que hacemos es crear una cookie, con datos adicionales (el ID del usuario).

Ahora lo que nos toca es la otra parte: reemplazar el valor de HttpContext.Current.User.Identity por uno propio que tenga el ID. Para ello usamos el evento PostAuthenticate_Request:

protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
CustomIdentity identity = new CustomIdentity(authTicket.Name, authTicket.UserData);
GenericPrincipal newUser = new GenericPrincipal(identity, new string[] {});
Context.User = newUser;
}
}

Recogemos la cookie de autenticación, desencriptamos el ticket de autenticación por forms y con los datos (el nombre y el UserData) creamos un objeto de tipo CustomIdentity, clase nuestra que nos implementa IIdentity. Luego la incrustamos dentro de un GenericPrincipal y lo establecemos a la propiedad User del HttpContext.

Nota: El segundo parámetro del constructor de GenericPrincipal es el array de roles a los que pertenece el usuario. En mi caso no uso roles, así que le asigno un array vacío.

La clase CustomIdentity es tal y como sigue:

class CustomIdentity : IIdentity
{

public CustomIdentity(string name, string id)
{
IsAuthenticated = true;
Name = name;
Id = Int32.Parse(id);
AuthenticationType = "Forms";
}

public string AuthenticationType { get; private set; }
public bool IsAuthenticated { get; private set; }
public string Name { get; private set;}
public int Id { get; private set; }
}

De esta manera, ahora podemos al Id del usuario, desde un controlador:

CustomIdentity ci = (CustomIdentity)ControllerContext.HttpContext.User.Identity;
int IdUsuario = ci.Id;

Un misterio con el que me he encontrado es que el código de PostAuthenticateRequest si se pone en AuthenticateRequest (que parece que debería funcionar igual), se queja diciendo que la clase “CustomIdentity” no es serializable. No tengo muy claro porque ocurre eso y eso si que parece ser propio de MVC. Aquí hay más información al respecto: http://stackoverflow.com/questions/1884030/implementing-a-custom-identity-and-iprincipal-in-mvc

Y Listos!

Con esto podemos acceder al ID de nuestros usuarios sin necesidad de usar para nada la base de datos. Además, dado que estamos usando el sistema de autenticación de ASP.NET (no hacemos nada raro), nos siguen funcionando los filtros de autenticación como [Authorize].

Un saludo!

Referencia: http://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *