I've got the following client set up in IdentityServer:
internal class Clients
{
public static IEnumerable<Client> Get()
{
return new List<Client>
{
new Client
{
ClientId = "Client",
ClientName = "Client",
ClientSecrets = new List<Secret> {new Secret("blablabla".Sha256())},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { Startup.Configuration.GetValue<string>("Clients", "https://localhost:5002") + "/signin-oidc" },
PostLogoutRedirectUris = { Startup.Configuration.GetValue<string>("Clients", "https://localhost:5002") + "/signout-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"roles",
"Business-to-Business-users",
"reporting-urls"
},
RequirePkce = true,
AllowPlainTextPkce = false,
AllowAccessTokensViaBrowser = true
}
};
}
}
I'm trying to modify the mvc client to login to the above identity server. In startup.cs I modified cllientID, ClientSecret and Authority to match the client settings. This is my ConfigureServices():
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie("cookie")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Configuration.GetValue<string>("IdentityHost", "https://localhost:5000");
options.ClientId = "Client";
options.ClientSecret = "blablabla";
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.ResponseType = "code";
options.UsePkce = true;
options.ResponseMode = "query";
options.Scope.Add("roles");
options.Scope.Add("Business-to-Business-users");
options.Scope.Add("reporting-urls");
options.RequireHttpsMetadata = false;
options.ClaimActions.MapUniqueJsonKey("role", "role");
options.ClaimActions.MapUniqueJsonKey("Business-to-Business-user", "Business-to-Business-user");
options.ClaimActions.MapUniqueJsonKey("reporting-url", "reporting-url");
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "role",
};
});
first I got a bug that he needs https but instead uses http. So I added the following code to my Configure:
app.Use((context, next) => {
context.Request.Scheme = "https";
return next();
});
Now that this error was solved, I ran into a different one.
As soon as I'm trying to login to my client, I got the following error in my browser (Opera): "ERR_TOO_MANY_REDIRECTS".
Does anyone have an idea how I can proceed? Both Identity and the client are seperate docker containers.
Related
I have a blazor app with azure ad auth, where human users are redirected to the microsoft log in screen etc, I now have a requirement that a tool needs to be able to access specific pages using a key in the header.
Is this possible? If so how do I add the second type of authorization where it effectively ignores the first type if the key is present?
I use the following code :
Connection Database
get conncentionString from appsettings.json :
var connectionString = builder.Configuration.GetConnectionString("AppContext") ?? throw new InvalidOperationException("Connection string 'AppContext' not found.");
Add DbContext :
builder.Services.AddDbContext<AppContext>(options =>
{
options.UseLazyLoadingProxies().UseSqlServer(connectionString, x => x.UseNetTopologySuite());
});
Add Identity
builder.Services.AddIdentity<IdentityUser, IdentityRole>(option => {
option.Password.RequireDigit = true;
option.Password.RequiredUniqueChars = 5;
option.Password.RequireLowercase = false;
option.Password.RequireNonAlphanumeric = false;
option.Password.RequireUppercase = false;
option.Password.RequiredLength = 16;
}).AddEntityFrameworkStores<AppContext>();
Add Authentication API JWT
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://Example.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("keyhash"))
};
});
Add Authentication Web Cookie
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options => {
options.LoginPath = "/Login";
options.LogoutPath = "/Logout";
options.AccessDeniedPath = "/AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromMinutes(100000);
});
Used packages
1. Microsoft.EntityFrameworkCore.Design Version="6.0.5"
2. Microsoft.EntityFrameworkCore.Proxies Version="6.0.5"
3. Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite Version="6.0.5"
Context
I have one web application seperated in two "utilities".
Web API
Razor Pages
So my app is both an API and a ASP.NET Razor Pages app, to authenticate on the Web API side I use JWT Bearer and on Web App side a simple Cookie.
Problem
When using Cookie Authentication, I followed Microsoft's Use cookie authentication without ASP.NET Core Identity and it's absolutely not working AT ALL.
I use a custom AuthManager with a SignInAsync method. The cookie is indeed created BUT the ClaimsPrincipal in my HttpContext is empty
I didn't find ANY SOLUTION AT ALL on internet, the only solutions that seemed viable were by using custom Middlewares but I don't even know where to start.
If anybody encountered the same problem as me.
Thanks
public async Task<bool> LogInAsync(string email, string password, bool rememberMe)
{
UserModel user = _userService.UserLogin(email, password).MapFromBLL();
if (user is null) return false;
user.Roles = _roleService.GetUserRoles(user.Id).Select(r => r.MapFromBLL());
if (user.Roles is null || user.Roles.Count() == 0) return false;
List<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Email),
new Claim("Stamp", user.SecurityStamp.ToString())
};
IEnumerable<Claim> roleClaims = user.Roles.Select(ur => new Claim(ClaimTypes.Role, ur.Name));
claims.AddRange(roleClaims);
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
AuthenticationProperties authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTime.Now.AddDays(_jwtModel.ExpirationInDays),
IsPersistent = true,
IssuedUtc = DateTime.Now,
};
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
try
{
await _httpAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties);
return true;
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
return false;
}
}
Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.None;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Forbidden";
options.EventsType = typeof(SecurityStampUpdatedCookieAuthenticationEvent);
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = jwtModel.Issuer,
ValidAudience = jwtModel.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtModel.Secret)),
ClockSkew = TimeSpan.Zero
};
});
EDIT 10-02-2022
I seperated the post in two chapters : "Context" and "Problem"
My solution
Ok, so I found a solution to my problem. It was, as usual after 10 hours of research, dumb. In my Startup.cs I used JwtBearerDefaults.AuthenticationScheme as DefaultAuthenticateScheme. So I changed it with CookieAuthenticationDefaults.AuthenticationScheme, and now by miracle IT WORKS. After logging in, my ClaimsPrincipal User is full and it retrieve correctly the cookie.
My new services.AddAuthentication() in Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
I don't really know if I should keep options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;. If anyone could enlight me.
I didn't change anything in my method public async Task<bool> SignInAsync() I just added await _httpAccessor.HttpContext.SignOutAsync() at the beginning just to be sure.
public async Task<bool> LogInAsync(string email, string password, bool rememberMe)
{
await LogOutAsync();
// The same code as before
}
The LogOutAsync() method
public async Task LogOutAsync()
{
try
{
await _httpAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
Problems encountered
Authorization
After fixing this problem, I had another one. Now that my default scheme is Cookie based, I can't just use [Authorize] and except it to work with both Cookie or Jwt. To fix that I just added this code in Startup.cs after services.AddAuthentication()
AuthorizationPolicy multiSchemePolicy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(options =>
{
options.DefaultPolicy = multiSchemePolicy;
});
Code that I found here
API Unauthorize return Login Page instead of 401 or 403 status codes
This one is kind of strange, now the cookie and the JwtBearer worked fine, when I tried to access an [Authorize] route, sometimes it returned me the HTML page to Login instead of 401 Status Code and sometimes not.
To avoid this problem, I found a solution here
using services.ConfigureApplicationCookie didn't work for me, instead I adapted my services.AddCookie().
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Forbidden";
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 401;
}
return Task.CompletedTask;
},
OnRedirectToAccessDenied = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 403;
}
return Task.CompletedTask;
}
};
options.EventsType = typeof(SecurityStampUpdatedCookieAuthenticationEvent);
});
VoilĂ , I hope I have helped at least one of you. If there is anything to change, just let me know !
Complete code
Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = jwtModel.Issuer,
ValidAudience = jwtModel.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtModel.Secret)),
ClockSkew = TimeSpan.Zero
};
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Forbidden";
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 401;
}
return Task.CompletedTask;
},
OnRedirectToAccessDenied = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 403;
}
return Task.CompletedTask;
}
};
options.EventsType = typeof(SecurityStampUpdatedCookieAuthenticationEvent);
});
AuthorizationPolicy multiSchemePolicy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(options =>
{
options.DefaultPolicy = multiSchemePolicy;
});
AuthManager.cs
public async Task<bool> LogInAsync(string email, string password, bool rememberMe)
{
await LogOutAsync();
UserModel user = _userService.UserLogin(email, password).MapFromBLL();
if (user is null) return false;
user.Roles = _roleService.GetUserRoles(user.Id).Select(r => r.MapFromBLL());
if (user.Roles is null || user.Roles.Count() == 0) return false;
List<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Email),
new Claim("Stamp", user.SecurityStamp.ToString())
};
IEnumerable<Claim> roleClaims = user.Roles.Select(ur => new Claim(ClaimTypes.Role, ur.Name));
claims.AddRange(roleClaims);
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
AuthenticationProperties authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTime.Now.AddDays(_jwtModel.ExpirationInDays),
IsPersistent = true,
IssuedUtc = DateTime.Now,
};
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
try
{
await _httpAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties);
return true;
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
return false;
}
}
public async Task LogOutAsync()
{
try
{
await _httpAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
I have been getting this error and i have searched for answer online and almost all of them has been talking about requesturis and i have double checked to make sure that my uris are configured properly. I did not find any clue about it. Any thoughts.
Identity Server 4 settings:-
public static IEnumerable<Client> GetClients()
{
return new List<Client>()
{
new Client
{
ClientName="KtsWeb App",
ClientId="ktswebclient",
AllowedGrantTypes= GrantTypes.Hybrid,
AccessTokenType = AccessTokenType.Reference,
RedirectUris = new List<string>()
{
"https://localhost:44355/signin-oidc" //Client URL Address
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
}
};
}
Client settings:-
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies",
(options) =>
{
options.AccessDeniedPath = "/Authorization/AccessDenied";
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:44380"; //Identity Server URL Address
options.ClientId = "ktswebclient";
options.ResponseType = "code id_token";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.SaveTokens = true;
});
You should always check the identity server logs it would have given you a clear error message probably unsupported grant type.
You have created a hybrid client but forgot to add the secret.
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.Hybrid,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
};
Your code isnt supplying the secret
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
options.ClaimActions.MapJsonKey("website", "website");
});
I have two web applications: one ASP.NET Core MVC Website and one ASP.NET Core Web API (using ASP.NET Core 2.1).
I authenticate the users in the website using Azure AD. When I pass the access_token from the website to the API (using the Authorization header with "Bearer access_token"); I have this error:
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException:
IDX10503: Signature validation failed. Keys tried:
'Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: ... '.
I don't understand why.
Here is the configuration of the Website:
services.AddAuthentication(o =>
{
o.DefaultChallengeScheme = "aad";
o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>
{
o.Authority = azureAdsettings.Authority;
o.Audience = azureAdsettings.ClientId;
})
.AddCookie()
.AddOpenIdConnect("aad", o =>
{
o.Authority = azureAdsettings.Authority;
o.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
o.ClientId = azureAdsettings.ClientId;
o.ClientSecret = azureAdsettings.ClientSecret;
o.ResponseType = OpenIdConnectResponseType.CodeIdToken;
o.SaveTokens = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = azureAdsettings.Authority
};
o.Events = new OpenIdConnectEvents
{
OnTicketReceived = ctx =>
{
return Task.CompletedTask;
},
OnAuthorizationCodeReceived = async ctx =>
{
TokenProvider tokenProvider = (TokenProvider)ctx.HttpContext.RequestServices.GetRequiredService<ITokenProvider>();
await tokenProvider.AcquireTokenByAuthorizationCodeAsync(ctx);
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
};
});
And here is the configuration of the API:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = this.Configuration["AzureAd:Authority"];
options.Audience = this.Configuration["AzureAd:ClientId"];
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.IssuerValidator = ValidateIssuer;
});
The azureAdsettings object is exaclty the same in both appsettings.json.
The authentication on the website seems to work nicely. I can get the authorization code and retreive access token.
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())
},
},
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));
})
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
I think this could help, it's included in the community samples.