J'essaie d'implémenter Identity Server 4 avec AspNet Core à l'aide du flux de code d'autorisation.
Le fait est que le référentiel IdentityServer4 sur github a plusieurs échantillons, mais aucun avec Flux de code d'autorisation .
Quelqu'un at-il un exemple sur la façon d'implémenter le flux de code d'autorisation avec Identity Server 4 et un client dans MVC qui le consomme?
Voici une implémentation d'un flux de code d'autorisation avec Identity Server 4 et un client MVC pour le consommer.
IdentityServer4 peut utiliser un fichier client.cs pour enregistrer notre client MVC, c'est ClientId, ClientSecret, les types de droits autorisés (Code d'autorisation dans ce cas) et le RedirectUri de notre client:
public class Clients
{
public static IEnumerable<Client> Get()
{
var secret = new Secret { Value = "mysecret".Sha512() };
return new List<Client> {
new Client {
ClientId = "authorizationCodeClient2",
ClientName = "Authorization Code Client",
ClientSecrets = new List<Secret> { secret },
Enabled = true,
AllowedGrantTypes = new List<string> { "authorization_code" }, //DELTA //IdentityServer3 wanted Flow = Flows.AuthorizationCode,
RequireConsent = true,
AllowRememberConsent = false,
RedirectUris =
new List<string> {
"http://localhost:5436/account/oAuth2"
},
PostLogoutRedirectUris =
new List<string> {"http://localhost:5436"},
AllowedScopes = new List<string> {
"api"
},
AccessTokenType = AccessTokenType.Jwt
}
};
}
}
Cette classe est référencée dans la méthode ConfigurationServices de Startup.cs dans le projet IdentityServer4:
public void ConfigureServices(IServiceCollection services)
{
////Grab key for signing JWT signature
////In prod, we'd get this from the certificate store or similar
var certPath = Path.Combine(PlatformServices.Default.Application.ApplicationBasePath, "SscSign.pfx");
var cert = new X509Certificate2(certPath);
// configure identity server with in-memory stores, keys, clients and scopes
services.AddDeveloperIdentityServer(options =>
{
options.IssuerUri = "SomeSecureCompany";
})
.AddInMemoryScopes(Scopes.Get())
.AddInMemoryClients(Clients.Get())
.AddInMemoryUsers(Users.Get())
.SetSigningCredential(cert);
services.AddMvc();
}
Pour référence, voici les classes Users et Scopes référencées ci-dessus:
public static class Users
{
public static List<InMemoryUser> Get()
{
return new List<InMemoryUser> {
new InMemoryUser {
Subject = "1",
Username = "user",
Password = "pass123",
Claims = new List<Claim> {
new Claim(ClaimTypes.GivenName, "GivenName"),
new Claim(ClaimTypes.Surname, "surname"), //DELTA //.FamilyName in IdentityServer3
new Claim(ClaimTypes.Email, "[email protected]"),
new Claim(ClaimTypes.Role, "Badmin")
}
}
};
}
}
public class Scopes
{
// scopes define the resources in your system
public static IEnumerable<Scope> Get()
{
return new List<Scope> {
new Scope
{
Name = "api",
DisplayName = "api scope",
Type = ScopeType.Resource,
Emphasize = false,
}
};
}
}
L'application MVC nécessite deux méthodes de contrôleur. La première méthode démarre le flux de travail du fournisseur de services (initié par SP). Il crée une valeur d'état, l'enregistre dans un middleware d'authentification basé sur les cookies, puis redirige le navigateur vers IdentityProvider (IdP) - notre projet IdentityServer4 dans ce cas.
public ActionResult SignIn()
{
var state = Guid.NewGuid().ToString("N");
//Store state using cookie-based authentication middleware
this.SaveState(state);
//Redirect to IdP to get an Authorization Code
var url = idPServerAuthUri +
"?client_id=" + clientId +
"&response_type=" + response_type +
"&redirect_uri=" + redirectUri +
"&scope=" + scope +
"&state=" + state;
return this.Redirect(url); //performs a GET
}
Pour référence, voici les constantes et la méthode SaveState utilisées ci-dessus:
//Client and workflow values
private const string clientBaseUri = @"http://localhost:5436";
private const string validIssuer = "SomeSecureCompany";
private const string response_type = "code";
private const string grantType = "authorization_code";
//IdentityServer4
private const string idPServerBaseUri = @"http://localhost:5000";
private const string idPServerAuthUri = idPServerBaseUri + @"/connect/authorize";
private const string idPServerTokenUriFragment = @"connect/token";
private const string idPServerEndSessionUri = idPServerBaseUri + @"/connect/endsession";
//These are also registered in the IdP (or Clients.cs of test IdP)
private const string redirectUri = clientBaseUri + @"/account/oAuth2";
private const string clientId = "authorizationCodeClient2";
private const string clientSecret = "mysecret";
private const string audience = "SomeSecureCompany/resources";
private const string scope = "api";
//Store values using cookie-based authentication middleware
private void SaveState(string state)
{
var tempId = new ClaimsIdentity("TempCookie");
tempId.AddClaim(new Claim("state", state));
this.Request.GetOwinContext().Authentication.SignIn(tempId);
}
La deuxième méthode d'action MVC est appelée par IdenityServer4 après que l'utilisateur a entré ses informations d'identification et a coché les cases d'autorisation. La méthode d'action:
Voici la méthode:
[HttpGet]
public async Task<ActionResult> oAuth2()
{
var authorizationCode = this.Request.QueryString["code"];
var state = this.Request.QueryString["state"];
//Defend against CSRF attacks http://www.twobotechnologies.com/blog/2014/02/importance-of-state-in-oauth2.html
await ValidateStateAsync(state);
//Exchange Authorization Code for an Access Token by POSTing to the IdP's token endpoint
string json = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(idPServerBaseUri);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", grantType)
,new KeyValuePair<string, string>("code", authorizationCode)
,new KeyValuePair<string, string>("redirect_uri", redirectUri)
,new KeyValuePair<string, string>("client_id", clientId) //consider sending via basic authentication header
,new KeyValuePair<string, string>("client_secret", clientSecret)
});
var httpResponseMessage = client.PostAsync(idPServerTokenUriFragment, content).Result;
json = httpResponseMessage.Content.ReadAsStringAsync().Result;
}
//Extract the Access Token
dynamic results = JsonConvert.DeserializeObject<dynamic>(json);
string accessToken = results.access_token;
//Validate token crypto
var claims = ValidateToken(accessToken);
//What is done here depends on your use-case.
//If the accessToken is for calling a WebAPI, the next few lines wouldn't be needed.
//Build claims identity principle
var id = new ClaimsIdentity(claims, "Cookie"); //"Cookie" matches middleware named in Startup.cs
//Sign into the middleware so we can navigate around secured parts of this site (e.g. [Authorized] attribute)
this.Request.GetOwinContext().Authentication.SignIn(id);
return this.Redirect("/Home");
}
Vérifier que l'État reçu correspond à ce que vous attendiez aide à se défendre contre les attaques CSRF: http://www.twobotechnologies.com/blog/2014/02/importance-of-state-in-oauth2.html
Cette méthode ValidateStateAsync compare l'état reçu à ce qui a été enregistré dans le middleware de cookie:
private async Task<AuthenticateResult> ValidateStateAsync(string state)
{
//Retrieve state value from TempCookie
var authenticateResult = await this.Request
.GetOwinContext()
.Authentication
.AuthenticateAsync("TempCookie");
if (authenticateResult == null)
throw new InvalidOperationException("No temp cookie");
if (state != authenticateResult.Identity.FindFirst("state").Value)
throw new InvalidOperationException("invalid state");
return authenticateResult;
}
Cette méthode ValidateToken utilise les bibliothèques System.IdentityModel et System.IdentityModel.Tokens.Jwt de Microsoft pour vérifier que JWT est correctement signé.
private IEnumerable<Claim> ValidateToken(string token)
{
//Grab certificate for verifying JWT signature
//IdentityServer4 also has a default certificate you can might reference.
//In prod, we'd get this from the certificate store or similar
var certPath = Path.Combine(Server.MapPath("~/bin"), "SscSign.pfx");
var cert = new X509Certificate2(certPath);
var x509SecurityKey = new X509SecurityKey(cert);
var parameters = new TokenValidationParameters
{
RequireSignedTokens = true,
ValidAudience = audience,
ValidIssuer = validIssuer,
IssuerSigningKey = x509SecurityKey,
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
//Validate the token and retrieve ClaimsPrinciple
var handler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var id = handler.ValidateToken(token, parameters, out jwt);
//Discard temp cookie and cookie-based middleware authentication objects (we just needed it for storing State)
this.Request.GetOwinContext().Authentication.SignOut("TempCookie");
return id.Claims;
}
Une solution de travail contenant ces fichiers source réside sur GitHub à https://github.com/bayardw/IdentityServer4.Authorization.Code
Voici un exemple - il utilise un flux hybride au lieu d'un flux de code. Mais le flux hybride est de toute façon plus recommandé si votre bibliothèque cliente le prend en charge (et le middleware aspnetcore le fait).