IdentifiantMot de passe
Loading...
Mot de passe oubli� ?Je m'inscris ! (gratuit)

Vous �tes nouveau sur Developpez.com ? Cr�ez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et �tre connect� pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Cr�ez-en un en quelques instants, c'est enti�rement gratuit !

Si vous disposez d�j� d'un compte et qu'il est bien activ�, connectez-vous � l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oubli� ?
Cr�er un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

IdentityServer4 : apprendre comment authentifier l'utilisateur via notre propre service d'acc�s aux donn�es et comment d�finir les revendications de l'utilisateur,
Un billet d'Hinault Romaric

Le , par Hinault Romaric

0PARTAGES

IdentityServer est une solution open source .NET de gestion d�identit� et de contr�le d�acc�s. Il repose sur les protocoles OpenID Connect et OAuth 2.0.

IdentityServer peut �tre utilis� par les entreprises pour mettre en place une solution pour :

  • la protection de leurs ressources ;
  • l�authentification des utilisateurs via une base de donn�es ou des fournisseurs externes d�identit� (Microsoft, Google, Facebook, etc.) ;
  • la gestion des sessions et la f�d�ration (single sign-on) ;
  • la g�n�ration des jetons pour les clients ;
  • la validation des jetons et bien plus.


Ce billet est le onzi�me que j��cris sur le sujet. Les billets pr�c�dents ont port� sur les points suivants :

Mise en place d�un STS avec IdentityServer4 pour s�curiser ses applications .NET

S�curisation d�une Web API ASP.NET Core avec le STS IdentityServer4

IdentityServer4 : cr�ation et configuration du Client pour acc�der � une Web API ASP.NET Core s�curis�e

IdentityServer4 : Authentification d�un utilisateur avec OpenID Connect

IdentityServer4 : cr�ation et configuration d�un client utilisant OpenID Connect

IdentityServer4 : Autoriser l�application MVC � acc�der � l�API, via le jeton obtenu du STS

IdentityServer4 : prise en charge du provider tiers Microsoft pour l'authentification

IdentityServer4 : prise en charge du provider tiers Github pour l'authentification

IdentityServer4 : persistance des donn�es de configuration avec EntityFramework Core

IdentityServer4 : utiliser ASP.NET Core Identity pour l�authentification

Dans l'un des billets pr�c�dents, nous avons vu comment utiliser OpenID et permettre � l�utilisateur de s�authentifier via un formulaire. Pour la mise en place de la fen�tre de connexion, de d�connexion, etc., nous avons utilis� un Quickstart offert par IdentityServer. Ce mod�le repose sur TestUserStore, qui nous permet de d�finir et charger nos utilisateurs depuis un fichier inclus dans le projet.

Le TestUserStore est offert � des fins de tests pour permettre aux d�veloppeurs de d�marrer facilement avec la prise en main de l�outil. Dans un projet concret d�entreprise, vous aurez votre propre base de donn�es utilisateurs et utiliserez ce dernier pour l�authentification.

Dans ce billet, nous verrons comment authentifier l�utilisateur en utilisant notre propre service d�acc�s aux donn�es et comment d�finir les revendications de l�utilisateur.

Nous utiliserons comme projet de base la solution suivante qui est disponible sur mon GitHub : https://github.com/hinault/identitys...ee/aspnetcore2.

Cr�ation du service

La premi�re chose � faire sera de d�finir notre classe entit� Utilisateur. Pour cela, nous allons cr�er dans le dossier Model la classe CustomUser suivante :

Code c# : S�lectionner tout
1
2
3
4
5
6
7
8
public class CustomUser 
    { 
        public string SubjectId { get; set; } 
        public string UserName { get; set; } 
        public string Password { get; set; } 
        public string FirstName { get; set; } 
        public string Email { get; set; } 
    }

La deuxi�me �tape sera la cr�ation du UserRepository. Nous allons tout d�abord cr�er l�interface IUserRepository dans le dossier Repository du projet :

Code c# : S�lectionner tout
1
2
3
4
5
6
7
8
9
public interface IUserRepository 
    { 
  
        CustomUser FindByUserName(string userName); 
  
        CustomUser FindBySubjectId(string subjectId); 
  
        bool ValidateCredentials(string userName, string password); 
    }


Ensuite, nous allons ajouter l�impl�mentation de cette interface :

Code c# : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class UserRepository : IUserRepository 
    { 
  
        private List<CustomUser> _customUsers = new List<CustomUser> { 
            new CustomUser 
            { 
                SubjectId = "1111", 
                UserName = "alice", 
                FirstName = "Alice Smith", 
                Email = "AliceSmith@email.com", 
                Password = "alice" 
            }, 
            new CustomUser 
            { 
                 SubjectId = "2222", 
                 UserName = "bob", 
                FirstName = "Bob Smith", 
                Email = "BobSmith@email.com", 
                Password = "bob" 
            } 
        }; 
  
        public CustomUser FindBySubjectId(string subjectId) 
        { 
            return _customUsers.Find(x => x.SubjectId.Equals(subjectId)); 
        } 
  
        public CustomUser FindByUserName(string userName) 
        { 
            return _customUsers.Find(x=>x.UserName.Equals(userName)); 
        } 
  
        public bool ValidateCredentials(string userName, string password) 
        { 
            var customUser = FindByUserName(userName); 
            return customUser != null && customUser.Password.Equals(password); 
        } 
    }

Afin que l�initialisation de ce service puisse se faire correctement, nous devons enregistrer celui-ci dans notre conteneur d�IoC ASP.NET Core. Vous devez �diter la m�thode ConfigureServices du fichier Startup.cs et ajouter la ligne de code suivante :

Code c# : S�lectionner tout
services.AddTransient<IUserRepository, UserRepository>();

Pour en savoir plus sur l�injection de d�pendances avec ASP.NET Core, veuillez consulter mon billet de blog suivant : https://www.developpez.net/forums/bl...-asp-net-core/

Le contr�leur AccourntController doit �tre modifi� pour utiliser le UserRepository et sa m�thode ValidateCredentials() pour valider l�identit� de l�utilisateur.

Code c# : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
private readonly IUserRepository _userRepository; 
  
        public AccountController( 
            IIdentityServerInteractionService interaction, 
            IClientStore clientStore, 
            IAuthenticationSchemeProvider schemeProvider, 
            IEventService events, 
           IUserRepository userRepository) 
        { 
  
            _interaction = interaction; 
            _clientStore = clientStore; 
            _schemeProvider = schemeProvider; 
            _events = events; 
            _userRepository = userRepository; 
        } 
  
        /// <summary> 
        /// Entry point into the login workflow 
        /// </summary> 
        [HttpGet] 
        public async Task<IActionResult> Login(string returnUrl) 
        { 
            // build a model so we know what to show on the login page 
            var vm = await BuildLoginViewModelAsync(returnUrl); 
  
            if (vm.IsExternalLoginOnly) 
            { 
                // we only have one option for logging in and it's an external provider 
                return RedirectToAction("Challenge", "External", new { provider = vm.ExternalLoginScheme, returnUrl }); 
            } 
  
            return View(vm); 
        } 
  
        /// <summary> 
        /// Handle postback from username/password login 
        /// </summary> 
        [HttpPost] 
        [ValidateAntiForgeryToken] 
        public async Task<IActionResult> Login(LoginInputModel model, string button) 
        { 
            // check if we are in the context of an authorization request 
            var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); 
  
            // the user clicked the "cancel" button 
            if (button != "login") 
            { 
                if (context != null) 
                { 
                    // if the user cancels, send a result back into IdentityServer as if they  
                    // denied the consent (even if this client does not require consent). 
                    // this will send back an access denied OIDC error response to the client. 
                    await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); 
  
                    // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null 
                    if (await _clientStore.IsPkceClientAsync(context.ClientId)) 
                    { 
                        // if the client is PKCE then we assume it's native, so this change in how to 
                        // return the response is for better UX for the end user. 
                        return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); 
                    } 
  
                    return Redirect(model.ReturnUrl); 
                } 
                else 
                { 
                    // since we don't have a valid context, then we just go back to the home page 
                    return Redirect("~/"); 
                } 
            } 
  
            if (ModelState.IsValid) 
            { 
                // validate username/password against in-memory store 
  
                if (_userRepository.ValidateCredentials(model.Username, model.Password)) 
                { 
                    var user = _userRepository.FindByUserName(model.Username); 
                    await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.SubjectId, user.UserName)); 
  
                    // only set explicit expiration here if user chooses "remember me".  
                    // otherwise we rely upon expiration configured in cookie middleware. 
                    AuthenticationProperties props = null; 
                    if (AccountOptions.AllowRememberLogin && model.RememberLogin) 
                    { 
                        props = new AuthenticationProperties 
                        { 
                            IsPersistent = true, 
                            ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) 
                        }; 
                    }; 
  
                    // issue authentication cookie with subject ID and username 
                    await HttpContext.SignInAsync(user.SubjectId, user.UserName, props); 
  
                    if (context != null) 
                    { 
                        if (await _clientStore.IsPkceClientAsync(context.ClientId)) 
                        { 
                            // if the client is PKCE then we assume it's native, so this change in how to 
                            // return the response is for better UX for the end user. 
                            return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); 
                        } 
  
                        // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null 
                        return Redirect(model.ReturnUrl); 
                    } 
  
                    // request for a local page 
                    if (Url.IsLocalUrl(model.ReturnUrl)) 
                    { 
                        return Redirect(model.ReturnUrl); 
                    } 
                    else if (string.IsNullOrEmpty(model.ReturnUrl)) 
                    { 
                        return Redirect("~/"); 
                    } 
                    else 
                    { 
                        // user might have clicked on a malicious link - should be logged 
                        throw new Exception("invalid return URL"); 
                    } 
                } 
  
                await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); 
                ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage); 
            }

Impl�mentation de l�interface IProfileService

Nous voulons que certaines informations de l�utilisateur (Email, nom, etc.) soient partag�es avec les applications qui viennent s�authentifier via IdentityServer. Ces informations doivent �tre incluses dans les Claims (Revendications). Pour mettre cela en place, nous devons fournir notre propre impl�mentation de l�interface IProfileService :

Code c# : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 public class ProfileService : IProfileService 
    { 
        private IUserRepository _userRepository { get; set; } 
        public ProfileService(IUserRepository userRepository) 
        { 
            _userRepository = userRepository; 
        } 
  
        public Task GetProfileDataAsync(ProfileDataRequestContext context) 
        { 
            var custormUser = _userRepository.FindBySubjectId(context.Subject.FindFirst(x => x.Type == "sub").Value); 
            if (custormUser != null) 
            { 
                context.IssuedClaims = GetClaims(custormUser); 
            } 
                return Task.FromResult(0); 
        } 
  
        public Task IsActiveAsync(IsActiveContext context) 
        { 
            return Task.FromResult(0); 
        } 
  
        private List<Claim> GetClaims(CustomUser customUser) => new List<Claim> { 
                new Claim(JwtClaimTypes.Name, customUser.UserName), 
                new Claim(JwtClaimTypes.FamilyName, customUser.FirstName), 
                new Claim(JwtClaimTypes.Email, customUser.Email) 
            }; 
    }

Modification de la configuration d�IdentityServer

Nous devons maintenant modifier la configuration d�IdentityServer pour enregistrer notre impl�mentation de IProfileService et supprimer l�enregistrement du TestUserStore :

services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddProfileService<ProfileService>();

C�est tout. Vous pouvez tester votre application et vous authentifier en utilisant l�application MvcAppClient :


Impl�mentation de l�interface IResourceOwnerPasswordValidator

Su vous souhaitez que d�autres clients puissent obtenir des jetons d�authentification en fournissant directement leur nom d�utilisateur et leur mot de passe au token endpoint, vous devez fournir votre propre impl�mentation de l�interface IResourceOwnerPasswordValidator.

Supposons que nous voulons que l�application ConsoleAppClient utilise ce mode. Nous allons dans un premier temps changer son GrantTypes dans le Config.cs pour utiliser ResourceOwnerPassword :

Code c# : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
  
  new Client 
                { 
                    ClientId = "consoleappclient", 
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, 
  
                    ClientSecrets = 
                    { 
                        new Secret("secret".Sha256()) 
                    }, 
                    AllowedScopes = { "testapi" } 
                },

Ensuite impl�menter l�interface IResourceOwnerPasswordValidator pour valider l�identit� de l�utilisateur en utilisant notre UserRepository :

Code c# : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator 
    { 
        private IUserRepository _userRepository { get; set; } 
        public ResourceOwnerPasswordValidator(IUserRepository userRepository) 
        { 
            _userRepository = userRepository; 
        } 
  
        public Task ValidateAsync(ResourceOwnerPasswordValidationContext context) 
        { 
            var customUser = _userRepository.FindByUserName(context.UserName); 
            if (customUser != null && customUser.Password.Equals(context.Password)) 
            { 
                context.Result = new GrantValidationResult( 
                 subject: customUser.SubjectId, 
                 authenticationMethod: OidcConstants.AuthenticationMethods.Password); 
                  } 
            else 
            { 
                context.Result = new GrantValidationResult( 
                    TokenRequestErrors.InvalidGrant, 
                    "invalid credential"); 
  
            } 
            return Task.FromResult(0); 
        } 
  
  
    }

Modifier la configuration de IdentityServer pour utiliser notre impl�mentation de cette interface :

Code c# : S�lectionner tout
1
2
3
4
5
6
      services.AddIdentityServer() 
                   .AddDeveloperSigningCredential() 
                   .AddInMemoryIdentityResources(Config.GetIdentityResources()) 
                    .AddInMemoryApiResources(Config.GetApiResources()) 
                      .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>() 
                  .AddProfileService<ProfileService>();


Maintenant, il ne nous reste plus qu�� mettre � jour le client pour passer ses informations d�identification lors de l�appel d�une ressource s�curis�e :

Code c# : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static async Task CallWebApiR() 
        { 
            // discover endpoints from metadata 
            var disco = await DiscoveryClient.GetAsync("https://localhost:5001"); 
            if (disco.IsError) 
            { 
                Console.WriteLine(disco.Error); 
                return; 
            } 
  
            // request token 
            var tokenClient = new TokenClient(disco.TokenEndpoint, "consoleappclient", "secret"); 
            var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice", "alice", "testapi"); 
  
            if (tokenResponse.IsError) 
            { 
                Console.WriteLine(tokenResponse.Error); 
                return; 
            } 
  
            Console.WriteLine(tokenResponse.Json); 
  
            // call api 
            var client = new HttpClient(); 
            client.SetBearerToken(tokenResponse.AccessToken); 
  
            var response = await client.GetAsync("https://localhost:5003/api/secure"); 
            if (!response.IsSuccessStatusCode) 
            { 
                Console.WriteLine(response.StatusCode); 
            } 
            else 
            { 
                var content = await response.Content.ReadAsStringAsync(); 
                Console.WriteLine(JArray.Parse(content)); 
            } 
        }

� l�ex�cution, on obtient ce qui suit :


Maintenant, vous �tes capables d'int�grer vos services d'acc�s aux donn�es d'authentification avec IdentityServer, fournir votre propre impl�mentation de certaines interfaces pour partager des informations dans les revendications et fournir votre propre m�canisme de validation de mot de passe.

R�f�rences :

Vous avez lu gratuitement 0 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer � vous proposer des publications.

Une erreur dans cette actualit� ? Signalez-nous-la !