We have a .Net Core 2.0 Web API project. I have added the hangfire there. We don't have any web page in the project and I use JWT for authorization. So I'm not able to do the authorization for hangfire using the Authorize(DashboardContext context). Is there any way we can pass some sort of API key on the url to authorize the user for dashboard?
Thanks
Yes, you can do that by using cookies, I will explain the idea to you with some code...
Firstly once the user login (you generate the token) you must store the token you generated to cookies in the browser and then when you want to access to Hangfire dashboard you must read the token from cookies and then check the roles...
the code to store the token in cookies:
`
httpContext.Response.Cookies.Append("token", userToken.AccessToken,
new Microsoft.AspNetCore.Http.CookieOptions { Expires = DateTime.Now.AddMinutes(6000) });
`
make sure you enabled cookies by:
`
AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie()
.AddJwtBearer(cfg => your configs);
`
then the authorize method will be like this:
`
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var jwtToken = string.Empty;
if (httpContext.Request.Cookies.ContainsKey("token"))
{
httpContext.Request.Cookies.TryGetValue("token", out jwtToken);
}
else
return false;
if (string.IsNullOrEmpty(jwtToken))
{
return false;
}
var handler = new JwtSecurityTokenHandler();
try
{
var claim = _tokenService.GetClaimsPrincipal(jwtToken);
return claim != null && claim.IsInRole(RolesConstants.ADMIN);
}
catch (Exception exception)
{
throw exception;
}
}
`
Implement an IAuthorizationDashboardFilter class.
Try this post here.
You need to pass JWT token with each request, try storing token in a cookie or as a query string parameter. Then you can pull that from request context and decide whether user is authorized
public class MyAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var token = context.Request.Cookies["access-token"];
// check token validity and set authentication accordingly.
return httpContext.User.Identity.IsAuthenticated;
}
}
Register the filter in your OWIN pipeline like this, after whatever authentication method you are using. Then the logged in user claim will be available in the filter
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(...); // Authentication - first
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new [] { new MyAuthorizationFilter() }
}); // Hangfire - last
}
Related
I am developing a Blazor WASM with authentication and authorization. The idea is that the user need to login in order to be able to view the Components of the Client Project but also to be able to consume data of Controllers from Server Project which are behind the /api.
Currently I have implemented the restriction on Client components:
<AuthorizeView>
<NotAuthorized>
<div class="row">
<div class="col-md-4">
<p>Please sign in to use the Platform...</p>
</div>
</div>
</NotAuthorized>
<Authorized>
#Body
</Authorized>
</AuthorizeView>
I have also a Login and a Logout Page which are storing a Cookie for later use and perform a custom AuthenticationStateProvider
await LocalStorage.SetItemAsync<int>($"{Parameters.application}_{Parameters.enviroment}_userid", authentication.user_id);
await LocalStorage.SetItemAsync<string>($"{Parameters.application}_{Parameters.enviroment}_username", authentication.user_name);
await AuthStateProvider.GetAuthenticationStateAsync();
The AuthenticationStateProvider code is the following:
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var state = new AuthenticationState(new ClaimsPrincipal());
string authcookie_name = $"{Parameters.application}_{Parameters.enviroment}_username";
string authcookie_value = await _localStorage.GetItemAsStringAsync(authcookie_name);
if (!string.IsNullOrEmpty(authcookie_value))
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Authentication, authcookie_value)
}, "Login");
state = new AuthenticationState(new ClaimsPrincipal(identity));
}
NotifyAuthenticationStateChanged(Task.FromResult(state));
return state;
}
The authentication controller is the following:
[HttpPost, Route("/api/auth/login")]
public IActionResult AuthLogin(Authentication authentication)
{
try
{
int auth = _IAuth.AuthLogin(authentication);
if (auth != -1)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Authentication, authentication.user_name)
};
var claimsIdentity = new ClaimsIdentity(claims, "Login");
var properties = new AuthenticationProperties()
{
IsPersistent = true
};
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), properties);
}
return Ok(auth);
}
catch { throw; }
}
Everything is working as excepted and the user need to login in order to see the content of the pages, but he is able to see the data of each page if he perform an http call http://domain.ext/api/model/view
In order to resolve this problem I added the Authorize attribute on each controller of Server project like this:
[Authorize]
[Route("/api/model")]
[ApiController]
public class Controller_Model : ControllerBase
{
}
And also added this code on the Program.cs of Server project in order to be able to make controller to work
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.SlidingExpiration = true;
options.LoginPath = new PathString("/auth/login");
options.LogoutPath = new PathString("/auth/logout");
options.Cookie = new CookieBuilder();
options.Cookie.MaxAge = options.ExpireTimeSpan;
options.AccessDeniedPath = "/";
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});
Now the user is not able to see the content of a page even he is making a request to the /api.
The problem is that after some time, even I see the User is still logged in the Authorize attribute of controllers is consider the user not authorized and it returns an error because controller is not returning the supposed object list.
I have no clue why and when this is happening. Then if user Logout and Login again it works for a while again.
===============UPDATE 1===============
After lot of investigation, seems that the client side is authenticated and then every time it sees the localstorage item it continues to be in authenticated state. On the other side the server state is based on a cookie which expires after 30mins.
So the Client and the Server states are operated differently and that's why the Client seems authenticated while Server is not while denying access on controllers.
I think the solution is to change the CustomAuthenticationStateProvider in order to check if the cookie exists and if it's valid. So the event order be as follow:
User SingIn via Client Page -> Server Controller creates the cookie -> Client Page is authenticated via Authentication State Provider which reads the cookie.
Any ideas?
Seems that is possible to read and write cookies from Client Project only via Javascript. What needs to be done is the following:
A custom javascript file "cookie.js", under wwwroot/js:
export function get() {
return document.cookie;
}
export function set(key, value) {
document.cookie = `${key}=${value}`;
}
A C# class file "CookieStorageAccessor.cs", under /Classes:
public class CookieStorageAccessor
{
private Lazy<IJSObjectReference> _accessorJsRef = new();
private readonly IJSRuntime _jsRuntime;
public CookieStorageAccessor(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
private async Task WaitForReference()
{
if (_accessorJsRef.IsValueCreated is false)
{
_accessorJsRef = new(await _jsRuntime.InvokeAsync<IJSObjectReference>("import", "/js/cookie.js"));
}
}
public async ValueTask DisposeAsync()
{
if (_accessorJsRef.IsValueCreated)
{
await _accessorJsRef.Value.DisposeAsync();
}
}
public async Task<T> GetValueAsync<T>(string key)
{
await WaitForReference();
var result = await _accessorJsRef.Value.InvokeAsync<T>("get", key);
return result;
}
public async Task SetValueAsync<T>(string key, T value)
{
await WaitForReference();
await _accessorJsRef.Value.InvokeVoidAsync("set", key, value);
}
}
The C# class can be used injecting javascript and reading the cookie on
CustomAuthStateProvider:
//CREATE INSTANCE OF COOKIE ACCESSOR
CookieStorageAccessor cookieStorageAccessor = new CookieStorageAccessor(_jSRuntime);
//CHECK IF COOKIE IS EXISTS FROM COOKIE ACCESSOR
string auth_cookie = await cookieStorageAccessor.GetValueAsync<string>("authentication");
if (!string.IsNullOrEmpty(auth_cookie))
{ }
else
{ }
Following on from this question, I ended up using the HttpContext.SignInAsync(string subject, Claim[] claims) overload to pass the selected tenant id as a claim (defined as type "TenantId") after the user selects a Tenant.
I then check for this claim in my custom AccountChooserResponseGenerator class from this question to determine if the user needs to be directed to the Tenant Chooser page or not, as follows:
public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)
{
var response = await base.ProcessInteractionAsync(request, consent);
if (response.IsConsent || response.IsLogin || response.IsError)
return response;
if (!request.Subject.HasClaim(c=> c.Type == "TenantId" && c.Value != "0"))
return new InteractionResponse
{
RedirectUrl = "/Tenant"
};
return new InteractionResponse();
}
The interaction is working and the user gets correctly redirected back to the Client app after selecting a Tenant.
However, on my client, I have the simple:
<dl>
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
</dl>
snippet from the IdentityServer4 quickstarts to show the claims, and sadly, my TenantId claim is not there.
I have allowed for it in the definition of my Client on my IdentityServer setup, as follows:
var client = new Client
{
... other settings here
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
"TenantId"
}
};
What am I missing in order for this TenantId claim to become visible in my Client application?
EDIT:
Based on #d_f's comments, I have now added TentantId to my server's GetIdentityResources(), as follows:
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResources.Phone(),
new IdentityResource("TenantId", new[] {"TenantId"})
};
}
And I have edited the client's startup.ConfigureServices(IServiceCollection services) to request this additional scope, as follows:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
//other settings not shown
options.Scope.Add("TenantId");
});
And still the only claims displayed on the client by the indicated snippet are:
Edit 2: Fixed!
Finally #RichardGowan's answer worked. And that is because (as brilliantly observed by #AdemCaglin) I was using IdentityServer's AspNetIdentity, which has it's own implementation of IProfileService, which kept dropping my custom TenantId claim, despite ALL these other settings).
So in the end, I could undo all those other settings...I have no mention of the TenantId claim in GetIdentityResources, no mention of it in AllowedScopes in the definition of the Client in my IdSrv, and no mention of it in the configuration of services.AddAuthentication on my client.
You will need to provide and register an implementation of IProfileService to issue your custom claim back to the client:
public class MyProfileService : IProfileService {
public MyProfileService() {
}
public Task GetProfileDataAsync(ProfileDataRequestContext context) {
// Issue custom claim
context.IssuedClaims.Add(context.Subject.Claims.First(c => c.Type ==
"TenantId"));
return Task.CompletedTask;
}
public Task IsActiveAsync(IsActiveContext context) {
context.IsActive = true;
return Task.CompletedTask;
}
}
I am using JWT authentication in my ASP.NET Core 2.0 application with OpenIddict.
I am following idea in this thread and calling AuthorizeWithJWT method after SignalR handshake. But now, I do not know what should I set in AuthorizeWithJWT method so I can use [Authorize(Roles="Admin")] for example.
I tried with setting context user, but it is readonly:
public class BaseHub : Hub
{
public async Task AuthorizeWithJWT(string AccessToken)
{
//get user claims from AccesToken
this.Context.User = user; //error User is read only
}
}
And using authorize attribute:
public class VarDesignImportHub : BaseHub
{
[Authorize(Roles = "Admin")]
public async Task Import(string ConnectionString)
{
}
}
I strongly encourage you to continue doing authentication at the handshake level instead of going with a custom and non-standard solution you'd implement at the SignalR level.
Assuming you're using the validation handler, you can force it to retrieve the access token from the query string:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddOAuthValidation(options =>
{
options.Events.OnRetrieveToken = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
};
});
}
Or OnMessageReceived if you want to use JWTBearer:
services.AddAuthentication()
.AddJwtBearer(o =>
{
o.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
if (context.Request.Path.ToString().StartsWith("/HUB/"))
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
},
};
});
No other change should be required.
Trying to configure my Web Api as Resource Server. My client logs into Auth0 and gets Bearer token, so Authorization Server is Auth0 not my Api. Then they send request along with the Bearer token to my Api. In my ASP.Net Web Api I have implemented following OWIN configuration in Startup class to validate the request JWT Bearer token issued by Auth0 as instructed here.
Statup:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var auth0Options = new Auth0Options()
{
Issuer = $"https://{ConfigurationManager.AppSettings["Auth0ApiInternalDomain"]}/",
Audience = ConfigurationManager.AppSettings["Auth0ApiInternalAudience"],
ClientId = ConfigurationManager.AppSettings["Auth0ApiInternalClientID"]
};
Auth0Config.Configure(app, auth0Options);
// Configure Web API
WebApiConfig.Configure(app);
}
}
and Auth0Config class:
public class Auth0Config
{
public static void Configure(IAppBuilder app, Auth0Options options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));
var keyResolver = new OpenIdConnectSigningKeyResolver(options.Issuer);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = options.Audience,
ValidIssuer = options.Issuer,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) => keyResolver.GetSigningKey(identifier),
ValidateLifetime = true,
ValidateIssuer = true,
ValidateAudience = true,
LifetimeValidator = (DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) =>
{
if (expires.Value < DateTime.UtcNow)
{
return false;
}
return true;
}
}
});
}
}
I pass Audience, Issuer and CliedntId from my app.config to this method. My intention is to figure out whether the Bearer token coming from the client to my Api is valid or not (here as first step I need to validate expiration date). When I debug my code for the incoming request, LifetimeValidator works fine and returns false for the expired token. I decorated my action with [Authorize] and expected to get 401 error but the actual response is 200 and it seems it ignores the LifetimeValidator implementation.
My action:
[Authorize]
public IHttpActionResult Get(string id)
{
var result = _bookingService.GetBooking(id);
if (result == null) return NotFound();
return Ok(result);
}
Am I missing something to get it right?
Is this a good approach to validate token expiration?
Is it possible to use OWIN only to validate the request Bearer token that has been issued out of web api application?
It turned out Invoke method of OwinMiddleware class had been overridden in my application to find Username from token and inject it to Request.User. Not sure why but somehow it ignores OWIN token validation functionality and didn't check Audience, Issuer or Expiration time.
public static void Configure(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
app.Use<HttpUsernameInjector>();
app.UseWebApi(config);
}
public class HttpUsernameInjector : OwinMiddleware
{
public HttpUsernameInjector(OwinMiddleware next)
: base(next){}
public override async Task Invoke(IOwinContext context)
{
const string usernameClaimKey = "myUserNameClaimKey";
var bearerString = context.Request.Headers["Authorization"];
if (bearerString != null && bearerString.StartsWith("Bearer ", StringComparison.InvariantCultureIgnoreCase))
{
var tokenString = bearerString.Substring(7);
var token = new JwtSecurityToken(tokenString);
var claims = token.Claims.ToList();
var username = claims.FirstOrDefault(x => x.Type == usernameClaimKey);
if (username == null) throw new Exception("Token should have username");
// Add to HttpContext
var genericPrincipal = new GenericPrincipal(new GenericIdentity(username.Value), new string[] { });
IPrincipal principal = genericPrincipal;
context.Request.User = principal;
}
await Next.Invoke(context);
}
}
by removing this class, OWIN token validation works fine!
Base on my research, the best token validation approaches in Web Api are OWIN and also IAuthenticationFilter.
It is possible as Resource Server and Authorization Server are decoupled. More info can be found here
Update
Found the solution here to stop OwinMiddleware suppressing my token validation logic
I have followed everything I know from posts regarding how to implement AspNet.Security.OpenIdConnect.Server.
Pinpoint, do you hear me? ;)
I've managed to separate token issuing and token consumption. I won't show the "auth server side" because I think that part is all set, but I'll show how I built the authentication ticket inside my custom AuthorizationProvider:
public sealed class AuthorizationProvider : OpenIdConnectServerProvider
{
// The other overrides are not show. I've relaxed them to always validate.
public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
{
// I'm using Microsoft.AspNet.Identity to validate user/password.
// So, let's say that I already have MyUser user from
//UserManager<MyUser> UM:
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
//identity.AddClaims(await UM.GetClaimsAsync(user));
identity.AddClaim(ClaimTypes.Name, user.UserName);
(await UM.GetRolesAsync(user)).ToList().ForEach(role => {
identity.AddClaim(ClaimTypes.Role, role);
});
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
// Some new stuff, per my latest research
ticket.SetResources(new[] { "my_resource_server" });
ticket.SetAudiences(new[] { "my_resource_server" });
ticket.SetScopes(new[] { "defaultscope" });
context.Validated(ticket);
}
}
And startup at the auth server:
using System;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Data.Entity;
using Microsoft.Extensions.DependencyInjection;
using MyAuthServer.Providers;
namespace My.AuthServer
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication();
services.AddCaching();
services.AddMvc();
string connectionString = "there is actually one";
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MyDbContext>(options => {
options.UseSqlServer(connectionString).UseRowNumberForPaging();
});
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler();
app.UseOpenIdConnectServer(options => {
options.ApplicationCanDisplayErrors = true;
options.AllowInsecureHttp = true;
options.Provider = new AuthorizationProvider();
options.TokenEndpointPath = "/token";
options.AccessTokenLifetime = new TimeSpan(1, 0, 0, 0);
options.Issuer = new Uri("http://localhost:60556/");
});
app.UseMvc();
app.UseWelcomePage();
}
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}
Sure enough, when I have this HTTP request, I do get an access token, but I'm not sure if that access token has all the data that the resource server expects.
POST /token HTTP/1.1
Host: localhost:60556
Content-Type: application/x-www-form-urlencoded
username=admin&password=pw&grant_type=password
Now, At the resource server side, I'm using JWT Bearer Authentication. On startup, I've got:
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Data.Entity;
using Microsoft.Extensions.DependencyInjection;
namespace MyResourceServer
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
string connectionString = "there is actually one";
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MyDbContext>(options => {
options.UseSqlServer(connectionString).UseRowNumberForPaging();
});
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler();
app.UseMvc();
app.UseWelcomePage();
app.UseJwtBearerAuthentication(options => {
options.Audience = "my_resource_server";
options.Authority = "http://localhost:60556/";
options.AutomaticAuthenticate = true;
options.RequireHttpsMetadata = false;
});
}
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}
When I make this HTTP request to the resource server, I get a 401 Unauthorized:
GET /api/user/myroles HTTP/1.1
Host: localhost:64539
Authorization: Bearer eyJhbGciOiJS...
Content-Type: application/json;charset=utf-8
The controller who has a route to /api/user/myroles is decorated with a plain [Authorize] with no parameters.
I feel like I'm missing something in both auth and resource servers, but don't know what they are.
The other questions that ask "how to validate token issued by AspNet.Security.OpenIdConnect.Server" don't have an answer. I would appreciate some help in this.
Also, I've noticed that there is OAuth Introspection commented out in the sample provider, and have read somewhere that Jwt is not going to be supported soon. I can't find the dependency that gives me the OAuth Instrospection.
UPDATE I've included both of my startup.cs, from each of auth and resource servers. Could there be anything wrong that would cause the resource server to always return a 401 for every request?
One thing I didn't really touch throughout this whole endeavor is signing. It seems to generate a signature for the JWT at the auth server, but the resource server (I guess) doesn't know the signing keys. Back in the OWIN projects, I had to create a machine key and put on the two servers.
Edit: the order of your middleware instances is not correct: the JWT bearer middleware must be registered before MVC:
app.UseIISPlatformHandler();
app.UseJwtBearerAuthentication(options => {
options.Audience = "my_resource_server";
options.Authority = "http://localhost:60556/";
options.AutomaticAuthenticate = true;
options.RequireHttpsMetadata = false;
});
app.UseMvc();
app.UseWelcomePage();
Sure enough, when I have this HTTP request, I do get an access token, but I'm not sure if that access token has all the data that the resource server expects.
Your authorization server and resource server configuration look fine, but you're not setting the "destination" when adding your claims (don't forget that to avoid leaking confidential data, AspNet.Security.OpenIdConnect.Server refuses to serialize the claims that don't explicitly specify a destination):
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, user.UserName, destination: "id_token token");
(await UM.GetRolesAsync(user)).ToList().ForEach(role => {
identity.AddClaim(ClaimTypes.Role, role, destination: "id_token token");
});
Also, I've noticed that there is OAuth Introspection commented out in the sample provider, and have read somewhere that Jwt is not going to be supported soon. I can't find the dependency that gives me the OAuth Instrospection.
Starting with the next beta (ASOS beta5, not yet on NuGet.org when writing this answer), we'll stop using JWT as the default format for access tokens, but of course, JWT will still be supported OTB.
Tokens now being opaque by default, you'll have to use either the new validation middleware (inspired from Katana's OAuthBearerAuthenticationMiddleware) or the new standard introspection middleware, that implements the OAuth2 introspection RFC:
app.UseOAuthValidation();
// Alternatively, you can also use the introspection middleware.
// Using it is recommended if your resource server is in a
// different application/separated from the authorization server.
//
// app.UseOAuthIntrospection(options => {
// options.AutomaticAuthenticate = true;
// options.AutomaticChallenge = true;
// options.Authority = "http://localhost:54540/";
// options.Audience = "resource_server";
// options.ClientId = "resource_server";
// options.ClientSecret = "875sqd4s5d748z78z7ds1ff8zz8814ff88ed8ea4z4zzd";
// });
You can find more information about these 2 middleware here: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/185