简体   繁体   中英

ASP.NET Boilerplate + IdentityServer

I tried to implement IdentityServer as it is explained at https://aspnetboilerplate.com/Pages/Documents/Zero/Identity-Server

But the sample does not work.

I started a Core 2.0 Angular project from ASP.NET Boilerplate. Is there any updated working sample based on the documentation?

There is more than one problem, but one of them is with AuthConfigurer.cs .

The API caller (client) cannot pass the token validation.

Actually, there is a token generation code in TokenAuthController.cs :

private string CreateAccessToken(IEnumerable<Claim> claims, TimeSpan? expiration = null)
{
    var now = DateTime.UtcNow;
    var jwtSecurityToken = new JwtSecurityToken(
        issuer: _configuration.Issuer,
        audience: _configuration.Audience,
        claims: claims,
        notBefore: now,
        expires: now.Add(expiration ?? _configuration.Expiration),
        signingCredentials: _configuration.SigningCredentials
    );
    return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}

But in the Startup class, AddIdentity and AddAuthentication create different token values and validation rules.

services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
        .AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
        .AddInMemoryClients(IdentityServerConfig.GetClients())
        .AddAbpPersistedGrants<IAbpPersistedGrantDbContext>()
        .AddAbpIdentityServer<User>(); ;

services.AddAuthentication().AddIdentityServerAuthentication("IdentityBearer", options =>
{
    options.Authority = "http://localhost:62114/";
    options.RequireHttpsMetadata = false;
});

The token can be generated by both sides. CreateAccessToken is called by the Angular client and by the API client as shown below:

var disco = await DiscoveryClient.GetAsync("http://localhost:21021");

var httpHandler = new HttpClientHandler();
httpHandler.CookieContainer.Add(new Uri("http://localhost:21021/"), new Cookie(MultiTenancyConsts.TenantIdResolveKey, "1")); //Set TenantId
var tokenClient = new TokenClient(disco.TokenEndpoint, "AngularSPA", "secret", httpHandler);
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("admin", "123qwe", "default-api"); //RequestClientCredentialsAsync("default-api");

But just one of them (according to the Authentication part) cannot pass authentication.

I need both the API client authentication and the Angular client authentication to work.

I have some clue from a link about dual authentication:
https://wildermuth.com/2017/08/19/Two-AuthorizationSchemes-in-ASP-NET-Core-2

But I could not solve this. Any comment is very valuable to solve the problem.

At the and I managed to solve problem here are the needed modifications;

1- in the TokenAuthController there is a token creation code as shown below;

private static List<Claim> CreateJwtClaims(ClaimsIdentity identity)
        {
            var claims = identity.Claims.ToList();
            var nameIdClaim = claims.First(c => c.Type == ClaimTypes.NameIdentifier);

            // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
            claims.AddRange(new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
            });

            return claims;
        }

If you start using Identityserver Claims coming from login is totally different from current implementation and "sub" claim is already added to claims. So it is not necessary to add seperately. So please update this as shown below

 private static List<Claim> CreateJwtClaims(ClaimsIdentity identity)
        {
            var claims = identity.Claims.ToList();

            // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
            claims.AddRange(new[]
            {
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
            });

            return claims;
        }

2- Add Authentcation to startup class as shown below; The most important part is authenticationSchemaName "IdentityBearer" do not forget adding it.

services.AddAuthentication().AddIdentityServerAuthentication("IdentityBearer", options =>
            {
                options.Authority = "http://localhost:21021/";
                options.RequireHttpsMetadata = false;
            });

3- But this is not enough. because if you look at configure methon in startup authontication is registered as

app.UseJwtTokenMiddleware(); 

if you check it it uses "bearer" schema not IdentityBearer as we added above. So we also need anpther authenticaiton registration. Add this line too (have both of them)

    app.UseJwtTokenMiddleware("IdentityBearer");

4- But as you can see there is no method taking string parameter to add UseJwtTokenMiddleware so it is needed to update that class to. please change your class as shown below;

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;

namespace MyProject.Authentication.JwtBearer
{
    public static class JwtTokenMiddleware
    {
        public static IApplicationBuilder UseJwtTokenMiddleware(this IApplicationBuilder app)
        {
            return UseJwtTokenMiddleware(app, JwtBearerDefaults.AuthenticationScheme);
        }

        public static IApplicationBuilder UseJwtTokenMiddleware(this IApplicationBuilder app, string authenticationScheme)
        {
            return app.Use(async (ctx, next) =>
            {
                if (ctx.User.Identity?.IsAuthenticated != true)
                {
                    var result = await ctx.AuthenticateAsync(authenticationScheme);
                    if (result.Succeeded && result.Principal != null)
                    {
                        ctx.User = result.Principal;
                    }
                }

                await next();
            });
        }        
    }
}

Now you have now two different token type and two different validator. YOu can have API client using basic token info and JWT tokens are created by login from angular client. if you debug each request tries to pass two of them but just one of them succeed which is enough for you.

if aspnetboilerplate team updates sample according to this requirement it would be great.

While I'm still trying to understand the whole process, I found this repository on GitHub very helpful when it comes to ABP and IdentityServer4. ABP documentation for IdentityServer is incomplete, download the IdentityServerDemo sample, run it and if successful compare it to your code:

https://github.com/parallelbgls/aspnetboilerplate-samples

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM