Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Identityserver4 and API in single project

I have an IdentityServer4 asp.net-core host setup for Resource Owner Password Grant using JWT Bearer tokens and an API in a separate asp.net-core host which has my API and two Angular clients.
The Authentication and Authorization is working from my two Angular clients to my API.
Now I need to expose an API in the IdentityServer4 host so I can create users from one of the Angular clients.
I have copied my Authentication and Authorization setup from my API over to my IdentityServer4 host, however, I cannot get it to Authenticate.

In the below code, within the API, I can set a breakpoint on the jwt.Authority... line and the first call will trigger this breakpoint in my API but not in the IdentityServer4 host.

Authentication

services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
  .AddJwtBearer(jwt =>
  {
    jwt.Authority = config.Authentication.Authority; //Breakpoint here
    jwt.RequireHttpsMetadata = config.Authentication.RequireHttpsMetadata;
    jwt.Audience = Common.Authorization.Settings.ServerApiName;
  });

Authorization

I'm not sure if it's relevant, but I'm using role based authorization, the following is the setup for this.

var authPolicyBuilder = new AuthorizationPolicyBuilder()
    .RequireRole(Common.Authorization.Settings.ServerApiRoleBasePolicyName)
    .Build();

services.AddMvc(options =>
    {
        options.Filters.Add(new AuthorizeFilter(authPolicyBuilder));

        ...

services.AddAuthorization(options =>
    {
        options.AddPolicy(Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName, policy =>
        {
            policy.RequireClaim("role", Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName);

I've extracted the following from my logging: What I see is that in the non-working case, I never get to the point of invoking the JWT validation (#3 in the working logs).
This is just a tiny extract of my logs, I can share them in entirety if needs be.

Working

1 Request starting HTTP/1.1 GET http://localhost:5100/packages/
(SourceContext:Microsoft.AspNetCore.Hosting.Internal.WebHost)
2 Connection id "0HLC8PLQH2NRU" started.
(SourceContext:Microsoft.AspNetCore.Server.Kestrel)
3 Request starting HTTP/1.1 GET http://localhost:5000/.well-known/openid-configuration
(SourceContext:Microsoft.AspNetCore.Hosting.Internal.WebHost)
--Truncated--

Not Working

1 Request starting HTTP/1.1 GET http://localhost:5000/users
(SourceContext:Microsoft.AspNetCore.Hosting.Internal.WebHost)
--Truncated--

Clients

new Client
{
    ClientId = "setup_app",
    ClientName = "Setup App",
    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    AccessTokenType = AccessTokenType.Jwt,
    AccessTokenLifetime = 3600,
    IdentityTokenLifetime = 3600,
    UpdateAccessTokenClaimsOnRefresh = true,
    SlidingRefreshTokenLifetime = 3600,
    AllowOfflineAccess = false,
    RefreshTokenExpiration = TokenExpiration.Absolute,
    RefreshTokenUsage = TokenUsage.OneTimeOnly,
    AlwaysSendClientClaims = true,
    Enabled = true,
    RequireConsent = false,
    AlwaysIncludeUserClaimsInIdToken = true,

    AllowedCorsOrigins = { config.CorsOriginSetupClient },


    ClientSecrets =
    {
        new Secret(Common.Authorization.Settings.ServerApiSetupClientSecret.Sha256())
    },

    AllowedScopes =
    {
        Common.Authorization.Settings.ServerApiName,
    }
},
new Client
{
    ClientId = "client_app",
    ClientName = "Client App",
    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    AccessTokenType = AccessTokenType.Jwt,
    AccessTokenLifetime = 3600,
    IdentityTokenLifetime = 3600,
    UpdateAccessTokenClaimsOnRefresh = true,
    SlidingRefreshTokenLifetime = 3600,
    AllowOfflineAccess = false,
    RefreshTokenExpiration = TokenExpiration.Absolute,
    RefreshTokenUsage = TokenUsage.OneTimeOnly,
    AlwaysSendClientClaims = true,
    Enabled = true,
    RequireConsent = false,
    AlwaysIncludeUserClaimsInIdToken = true,

    AllowedCorsOrigins = { config.CorsOriginSetupClient },


    ClientSecrets =
    {
        new Secret(Common.Authorization.Settings.ServerApiAppClientSecret.Sha256())
    },

    AllowedScopes =
    {
        Common.Authorization.Settings.ServerApiName,
    }
}

IdentityResources

return new List<IdentityResource>
{
    new IdentityResources.OpenId(),
    new IdentityResources.Profile(),
    new IdentityResource(Common.Authorization.Settings.ServerApiScopeName, new []{
        "role",
        Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName,
        Common.Authorization.Settings.ServerApiAppClientAdminRolePolicyName,
        Common.Authorization.Settings.ServerApiAppClientUserRolePolicyName,
}),
};

User

var adminUser = new ApplicationUser
{
    UserName = "admin",
    Email = "admin@noreply",
};
adminUser.Claims = new List<IdentityUserClaim>
{
    new IdentityUserClaim(new Claim(JwtClaimTypes.PreferredUserName, adminUser.UserName)),
    new IdentityUserClaim(new Claim(JwtClaimTypes.Email, adminUser.Email)),
    new IdentityUserClaim(new Claim("role", Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName)),
    new IdentityUserClaim(new Claim("role", Common.Authorization.Settings.ServerApiRoleBasePolicyName)),
    new IdentityUserClaim(new Claim("profileImage", $"https://robohash.org/{Convert.ToBase64String(System.Security.Cryptography.MD5.Create().ComputeHash(System.Text.Encoding.UTF8.GetBytes(adminUser.UserName)))}?set=set2"))
};
adminUser.AddRole(Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName);

API

new ApiResource(Common.Authorization.Settings.ServerApiName, "Server API"){
    ApiSecrets =
    {
        new Secret(Common.Authorization.Settings.ServerApiAppClientSecret.Sha256())
    },
}, 
like image 321
John B Avatar asked Mar 13 '18 10:03

John B


2 Answers

Look up here https://github.com/IdentityServer/IdentityServer4.Samples

Seems like it should be like:

Authentication:

services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = config.Authentication.Authority;

                options.RequireHttpsMetadata = false;

                options.ApiName = ServerApiName;
                options.ApiSecret = ServerApiAppClientSecret;
            });

Or with JWT you can try like:

services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(options =>
        {
            options.Authority = config.Authentication.Authority;
            options.RequireHttpsMetadata = false;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = true,
                ValidAudiences = new[]
                {
                    $"{config.Authentication.Authority}/resources",
                    ServerApiName
                },
            };
        });

Also, you will able to add authorization policy, like:

Authorization:

services.AddMvc(opt =>
            {
                var policy = new AuthorizationPolicyBuilder()
                       .RequireAuthenticatedUser()
                       .RequireScope("api").Build();
                opt.Filters.Add(new AuthorizeFilter(policy));
            })
like image 184
Oleksandr Nahirniak Avatar answered Sep 25 '22 15:09

Oleksandr Nahirniak


There is a MS out of the box service designed to add support for local APIs

First in IDS4 startup.cs, ConfigureServices add

services.AddLocalApiAuthentication();

Then in IDS4 config.cs in your client declaration

AllowedScopes = {
     IdentityServerConstants.LocalApi.ScopeName,  <<<< ---- This is for IDS4 Api access
     IdentityServerConstants.StandardScopes.OpenId,
     IdentityServerConstants.StandardScopes.Profile,
     IdentityServerConstants.StandardScopes.Email
     ...

Still in config add the new scope to ApiScopes

new ApiScope(IdentityServerConstants.LocalApi.ScopeName)

Note

IdentityServerConstants.LocalApi.ScopeName resolves to 'IdentityServerApi'

Now in your shiny new Api located in the IDS4 project add the authorize tag to your api endpoint

[Authorize(IdentityServerConstants.LocalApi.PolicyName)]
[HttpGet]
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

Finally you need to request this new scope in your Client

Scope = "openid sig1 api1 profile email offline_access company IdentityServerApi",

Thats it

like image 24
tinmac Avatar answered Sep 23 '22 15:09

tinmac