Como estender as propriedades disponíveis do User.Identity

130

Estou usando o MVC5 Identity 2.0 para que os usuários façam login no meu site, onde os detalhes da autenticação são armazenados em um banco de dados SQL. A identidade do Asp.net foi implementada de maneira padrão, como pode ser encontrado em muitos tutoriais online.

A classe ApplicationUser em IdentityModels foi estendida para incluir algumas propriedades personalizadas, como um OrganizationId inteiro. A ideia é que muitos usuários possam ser criados e atribuídos a uma organização comum para fins de relacionamento com o banco de dados.

public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }

        //Extended Properties
        public DateTime? BirthDate { get; set; }
        public long? OrganizationId { get; set; }

        //Key Mappings
        [ForeignKey("OrganizationId")]
        public virtual Organization Organization { get; set; }
    }

Como recuperar a propriedade OrganizationId do usuário conectado no momento de dentro de um controlador? Isso está disponível por meio de um método depois que um usuário está logado ou eu sempre recupero o OrganizationId do banco de dados, com base no UserId, toda vez que um método do controlador é executado?

Lendo na web que eu vi, preciso usar o seguinte para obter o UserId conectado etc.

using Microsoft.AspNet.Identity;
...
User.Identity.GetUserId();

No entanto, OrganizationId não é uma propriedade disponível no User.Identity. Preciso estender User.Identity para incluir a propriedade OrganizationId? Se sim, como faço para fazer isso.

O motivo pelo qual preciso do OrganizationId com tanta frequência é que muitas consultas de tabela dependem do OrganizationId para recuperar dados relevantes para a organização associada ao usuário conectado.

RobHurd
fonte
3
A minha resposta aqui ajuda em tudo?
Shoe
1
Praticamente a mesma resposta de mim aqui: stackoverflow.com/a/28138594/809357 - se você precisar dessas informações regularmente na vida da solicitação, poderá colocá-las no cookie como uma reivindicação.
TRAILMAX
1
Obrigado @Shoe, ambas as respostas funcionaram. Além de suas respostas, eu tive que adicionar uma reivindicação para ser armazenada no cookie. Na classe IdentityModels, era necessário adicionar userIdentity.AddClaim (new Claim ("MyApp: OrganizationId", OrganizationId.ToString ())); ao público assíncrono Tarefa <ClaimsIdentity> GenerateUserIdentityAsync (gerenciador de UserManager <ApplicationUser>) método .
RobHurd

Respostas:

219

Sempre que você desejar estender as propriedades de User.Identity com propriedades adicionais, como a pergunta acima, adicione essas propriedades à classe ApplicationUser primeiro da seguinte maneira:

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }

    // Your Extended Properties
    public long? OrganizationId { get; set; }
}

Então o que você precisa é criar um método de extensão assim (eu crio o meu em uma nova pasta Extensões):

namespace App.Extensions
{
    public static class IdentityExtensions
    {
        public static string GetOrganizationId(this IIdentity identity)
        {
            var claim = ((ClaimsIdentity)identity).FindFirst("OrganizationId");
            // Test for null to avoid issues during local testing
            return (claim != null) ? claim.Value : string.Empty;
        }
    }
}

Quando você cria a identidade na classe ApplicationUser, basta adicionar o Claim -> OrganizationId da seguinte forma:

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here => this.OrganizationId is a value stored in database against the user
        userIdentity.AddClaim(new Claim("OrganizationId", this.OrganizationId.ToString()));

        return userIdentity;
    }

Depois de adicionar a reivindicação e ter seu método de extensão, para disponibilizá-la como uma propriedade no User.Identity, adicione uma instrução using na página / arquivo que você deseja acessá-la :

no meu caso: using App.Extensions;dentro de um Controller e @using. App.Extensionsdentro de um arquivo .cshtml View.

EDITAR:

O que você também pode fazer para evitar adicionar uma instrução using em cada View é ir para a pasta Views e localizar o arquivo Web.config lá. Agora procure a <namespaces>tag e adicione o espaço para nome da sua extensão da seguinte maneira:

<add namespace="App.Extensions" />

Salve seu arquivo e pronto. Agora, todas as visualizações conhecem suas extensões.

Você pode acessar o método de extensão:

var orgId = User.Identity.GetOrganizationId();
Pawel
fonte
Oi pawel, eu tentei o mesmo, mas recebendo erro que o usuário do aplicativo não contém uma definição de OrganisationId
É uma armadilha
@RachitGupta Quando isso acontece? Ao tentar adicionar a reivindicação ou ao tentar acessar seu valor posteriormente no código? Se for quando você estiver adicionando uma declaração, verifique se o ApplicationUser tem a propriedade definida ... Se for mais tarde no código, não esqueça de adicionar a instrução using ao local em que você criou o método de extensão, como: using App.Extensions ;
Pawel
Erro na linha userIdentity.AddClaim. Eu criei a classe IdentityExtensions apenas no arquivo IdentityModels.cs. Isso pode ser uma fonte de problema?
É uma armadilha
Não, se é quando você adiciona a reivindicação, isso acontece muito antes de o identityExtensions entrar em jogo (é quando você lê o valor de volta) ... Verifique se o ApplicationUser tem a propriedade que você está tentando adicionar como reivindicação: neste exemplo, foi Organização pública longa {get; conjunto; }
Pawel
6
Obrigado, funcionou. Eu acho que essa é a melhor prática para variáveis ​​personalizadas no Asp Net Idendity 2. Não sei por que a comunidade Asp.Net não fornece esse exemplo em artigos padrão em seus sites.
oneNiceFriend
17

Eu estava procurando a mesma solução e Pawel me deu 99% da resposta. A única coisa que faltava para que a Extensão fosse exibida foi adicionar o seguinte Código Razor na página cshtml (exibição):

@using programname.Models.Extensions

Eu estava procurando pelo Nome, para exibir no canto superior direito da minha NavBar após o usuário fazer login.

Eu pensei que iria postar este caso ajuda alguém, então aqui está o meu código:

Criei uma nova pasta chamada Extensions (Under my Models Folder) e criei a nova classe como Pawel especificado acima: IdentityExtensions.cs

using System.Security.Claims;
using System.Security.Principal;

namespace ProgramName.Models.Extensions
{
    public static class IdentityExtensions
    {
        public static string GetUserFirstname(this IIdentity identity)
        {
            var claim = ((ClaimsIdentity)identity).FindFirst("FirstName");
            // Test for null to avoid issues during local testing
            return (claim != null) ? claim.Value : string.Empty;
        }
    }
}

IdentityModels.cs :

public class ApplicationUser : IdentityUser
{

    //Extended Properties
    public string FirstName { get; internal set; }
    public string Surname { get; internal set; }
    public bool isAuthorized { get; set; }
    public bool isActive { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        userIdentity.AddClaim(new Claim("FirstName", this.FirstName));

        return userIdentity;
    }
}

Então, nas minhas _LoginPartial.cshtml(Under Views/SharedFolders), adicionei@using.ProgramName.Models.Extensions

Em seguida, adicionei a alteração à linha de código a seguir que usaria o nome do usuário após o login:

@Html.ActionLink("Hello " + User.Identity.GetUserFirstname() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })

Talvez isso ajude alguém mais na linha.

AxleWack
fonte
11

Confira esta excelente postagem de blog de John Atten: ASP.NET Identity 2.0: Personalizando usuários e funções

Possui ótimas informações passo a passo sobre todo o processo. Vá ler :)

Aqui estão alguns dos princípios.

Estenda a classe ApplicationUser padrão adicionando novas propriedades (por exemplo, endereço, cidade, estado etc.):

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> 
    GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager.CreateIdentityAsync(this,  DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }

    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode { get; set; }

    // Concatenate the address info for display in tables and such:
    public string DisplayAddress
    {
        get
        {
            string dspAddress = string.IsNullOrWhiteSpace(this.Address) ? "" : this.Address;
            string dspCity = string.IsNullOrWhiteSpace(this.City) ? "" : this.City;
            string dspState = string.IsNullOrWhiteSpace(this.State) ? "" : this.State;
            string dspPostalCode = string.IsNullOrWhiteSpace(this.PostalCode) ? "" : this.PostalCode;

            return string.Format("{0} {1} {2} {3}", dspAddress, dspCity, dspState, dspPostalCode);
        }
    }

Em seguida, você adiciona suas novas propriedades ao seu RegisterViewModel.

    // Add the new address properties:
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }

Atualize a exibição do registro para incluir as novas propriedades.

    <div class="form-group">
        @Html.LabelFor(m => m.Address, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Address, new { @class = "form-control" })
        </div>
    </div>

Atualize o método Register () no AccountController com as novas propriedades.

    // Add the Address properties:
    user.Address = model.Address;
    user.City = model.City;
    user.State = model.State;
    user.PostalCode = model.PostalCode;
Exaustão
fonte
16
Este é um bom exemplo, mas não responde à pergunta, como você obtém essas novas propriedades do User.Identity.
Dejan Bogatinovski
5
Voto negativo porque a resposta não mostra como as propriedades personalizadas podem ser recuperadas de User.Identity.
maulik13
3

Para quem encontrar essa pergunta procurando como acessar propriedades personalizadas no ASP.NET Core 2.1 - é muito mais fácil: você terá um UserManager, por exemplo, em _LoginPartial.cshtml, e poderá fazer isso simplesmente (assumindo que "ScreenName" seja um propriedade que você adicionou ao seu próprio AppUser, que herda de IdentityUser):

@using Microsoft.AspNetCore.Identity

@using <namespaceWhereYouHaveYourAppUser>

@inject SignInManager<AppUser> SignInManager
@inject UserManager<AppUser> UserManager

@if (SignInManager.IsSignedIn(User)) {
    <form asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })" 
          method="post" id="logoutForm" 
          class="form-inline my-2 my-lg-0">

        <ul class="nav navbar-nav ml-auto">
            <li class="nav-item">
                <a class="nav-link" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">
                    Hello @((await UserManager.GetUserAsync(User)).ScreenName)!
                    <!-- Original code, shows Email-Address: @UserManager.GetUserName(User)! -->
                </a>
            </li>
            <li class="nav-item">
                <button type="submit" class="btn btn-link nav-item navbar-link nav-link">Logout</button>
            </li>
        </ul>

    </form>
} else {
    <ul class="navbar-nav ml-auto">
        <li class="nav-item"><a class="nav-link" asp-area="Identity" asp-page="/Account/Register">Register</a></li>
        <li class="nav-item"><a class="nav-link" asp-area="Identity" asp-page="/Account/Login">Login</a></li>
    </ul>
}
Jashan
fonte
2
Note-se que GetUserAsync(User)irá consultar o banco de dados para recuperar o OrganizationId. Por outro lado, a solução aceita incluirá o OrganizationId nas reivindicações (por exemplo, cookie). A vantagem de extrair essas informações do banco de dados é que as pessoas podem ser movidas entre organizações sem exigir que elas efetuem logout / logon. Obviamente, a desvantagem é que requer uma consulta adicional ao banco de dados.
Matt
1

O escape fornece uma boa maneira de adicionar a propriedade à classe ApplicationUser. Olhando para o código OP, parece que eles podem ter feito isso ou estavam no caminho certo. A pergunta faz

Como recuperar a propriedade OrganizationId do usuário conectado no momento de dentro de um controlador? No entanto, OrganizationId não é uma propriedade disponível no User.Identity. Preciso estender User.Identity para incluir a propriedade OrganizationId?

Pawel fornece uma maneira de adicionar um método de extensão que requer o uso de instruções ou a adição do namespace ao arquivo web.config.

No entanto, a pergunta pergunta se você "precisa" estender User.Identity para incluir a nova propriedade. Existe uma maneira alternativa de acessar a propriedade sem estender User.Identity. Se você seguiu o método de escape, poderá usar o código a seguir no seu controlador para acessar a nova propriedade.

ApplicationDbContext db = new ApplicationDbContext();
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
var currentUser = manager.FindById(User.Identity.GetUserId());
var myNewProperty = currentUser.OrganizationId;
codeMethod
fonte
0

Eu também adicionei ou estendi colunas adicionais à minha tabela AspNetUsers. Quando eu queria simplesmente visualizar esses dados, encontrei muitos exemplos como o código acima com "Extensões", etc ... Isso realmente me surpreendeu que você tivesse que escrever todas essas linhas de código apenas para obter alguns valores dos usuários atuais.

Acontece que você pode consultar a tabela AspNetUsers como qualquer outra tabela:

 ApplicationDbContext db = new ApplicationDbContext();
 var user = db.Users.Where(x => x.UserName == User.Identity.Name).FirstOrDefault();
Anthony Griggs
fonte