problem
How to get response body on invoke next context using custom middle ware ??
After reach to line of await _next.Invoke(context) from debug;
not return json data from action result getusermenu
[HttpGet(Contracts.ApiRoutes.Security.GetUserMenus)]
public IActionResult GetUserMenu(string userId)
{
string strUserMenus = _SecurityService.GetUserMenus(userId);
return Ok(strUserMenus);
}
I need to get response body from action result above
header :
key : Authorization
value : ehhhheeedff .
my code i try it :
public async Task InvokeAsync(HttpContext context, DataContext dataContext)
{
// than you logic to validate token
if (!validKey)
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await context.Response.WriteAsync("Invalid Token");
}
//if valid than next middleware Invoke
else
{
await _next.Invoke(context);
// i need after that get result on last of thread meaning return data of usermenu
}
}
}
public static class TokenExtensions
{
public static IApplicationBuilder UseTokenAuth(this IApplicationBuilder builder)
{
return builder.UseMiddleware<TokenValidateMiddleware>();
}
}
[no return data from access token][1]
https://i.stack.imgur.com/PHUMs.png
if(validtoken)
{
continue display getusermenuaction and show result
}
when valid token it return data like below on browser googlechrom
[
{
"form_name": "FrmAddPrograms",
"title": "Adding Screens",
"url": "",
"permissions": {
"Insert": "True",
"Edit": "True",
"Read": "True",
"Delete": "True",
"Print": "True",
"Excel": "False",
"RecordList": "False"
}
},
but on my app browser return
not valid token
Try to get response body in custom middleware using below code:
public class CustomMiddleware
{
private readonly RequestDelegate next;
public CustomMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (!validKey)
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await context.Response.WriteAsync("Invalid Token");
}
//if valid than next middleware Invoke
else
{
Stream originalBody = context.Response.Body;
try
{
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
await next(context);
memStream.Position = 0;
string responseBody = new StreamReader(memStream).ReadToEnd();//get response body here after next.Invoke()
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
}
finally
{
context.Response.Body = originalBody;
}
}
}
}
Related
https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-tutorial-build-blazor-server-chat-app
How do I get this to work with Azure AD activated? It works perfect when I run in locally in visual studio, but when deployed it will not work with Azure AD, only if I remove Azure AD it works.
This is the error message when deployed and after clicking the button "Chat!" next to username textbox:
"ERROR: Failed to start chat client: Response status code does not indicate success: 403 (Forbidden)."
(I have found other threads like this Blazor Server SignalR Chat works on Local, not on Azure but no solution)
//Program.cs
using BlazorApp6ADChat;
using BlazorApp6ADChat.Data;
using BlazorChat;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.MapHub<BlazorChatSampleHub>(BlazorChatSampleHub.HubUrl);
app.UseAuthentication();
app.UseAuthorization();
app.Run();
//appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "xxx.onmicrosoft.com",
"TenantId": "xxx",
"ClientId": "xxx",
"CallbackPath": "/signin-oidc"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Not sure if this will help. This is how I wire up a WebAssembly Host (server) with a SignalR Hub.
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>,
ConfigureJwtBearerOptions>());
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
{
public void PostConfigure(string name, JwtBearerOptions options)
{
var originalOnMessageReceived = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
await originalOnMessageReceived(context);
if (string.IsNullOrEmpty(context.Token))
{
var accessToken = context.Request.Query["access_token"];
var requestPath = context.HttpContext.Request.Path;
var endPoint = $"/chathub";
if (!string.IsNullOrEmpty(accessToken) &&
requestPath.StartsWithSegments(endPoint))
{
context.Token = accessToken;
}
}
};
}
}
I posted a solution here: Microsoft Learn
Basically:
Manually getting the cookies in the host.cshtml page
Passing the Cookie collection to the app.razor so I can create a cascading parameter
Retrieving the parameter and manually populating the cookie container when instantiating the SignalR client (you may have seen this code on the web, but without steps 1 and 2 it will not work on Azure only in IIS Express)
Program.cs
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAutoMapper(typeof(Program).Assembly);
builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<HttpContextAccessor>();
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
var app = builder.Build();
app.UseResponseCompression();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapBlazorHub();
app.MapHub<ChatHub>("/chathub");
app.MapFallbackToPage("/_Host");
app.Run();
}
}
_Host.cshtml:
<body>
#{
var CookieCollection = HttpContext.Request.Cookies;
Dictionary<string, string> Cookies = new Dictionary<string, string>();
foreach (var cookie in CookieCollection)
{
Cookies.Add(cookie.Key, cookie.Value);
}
}
<component type="typeof(App)" render-mode="ServerPrerendered" param-Cookies="Cookies" />
app.razor:
<CascadingValue Name="Cookies" Value="Cookies">
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
<FocusOnNavigate RouteData="#routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
</CascadingValue>
#code {
[Parameter] public Dictionary<string, string> Cookies { get; set; }
}
Index.razor:
#code {
#nullable disable
[CascadingParameter(Name = "Cookies")] public Dictionary<string, string> Cookies { get; set; }
System.Security.Claims.ClaimsPrincipal CurrentUser;
private HubConnection hubConnection;
private List<string> messages = new List<string>();
private string userInput;
private string messageInput;
private string strError = "";
protected override async Task OnInitializedAsync()
{
try
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
CurrentUser = authState.User;
// ** SignalR Chat
try
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"), options =>
{
options.UseDefaultCredentials = true;
var cookieCount = Cookies.Count();
var cookieContainer = new CookieContainer(cookieCount);
foreach (var cookie in Cookies)
cookieContainer.Add(new Cookie(
cookie.Key,
WebUtility.UrlEncode(cookie.Value),
path: "/",
domain: Navigation.ToAbsoluteUri("/").Host));
options.Cookies = cookieContainer;
foreach (var header in Cookies)
options.Headers.Add(header.Key, header.Value);
options.HttpMessageHandlerFactory = (input) =>
{
var clientHandler = new HttpClientHandler
{
PreAuthenticate = true,
CookieContainer = cookieContainer,
UseCookies = true,
UseDefaultCredentials = true,
};
return clientHandler;
};
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
}
catch(Exception ex)
{
strError = ex.Message;
}
}
catch
{
// do nothing if this fails
}
}
// ** SignalR Chat
private async Task Send()
{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", messageInput);
}
}
public bool IsConnected =>
hubConnection?.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
ChatHub.cs:
[Authorize]
public class ChatHub : Hub
{
public async override Task OnConnectedAsync()
{
if (this.Context.User?.Identity?.Name != null)
{
await Clients.All.SendAsync(
"broadcastMessage",
"_SYSTEM_",
$"{Context.User.Identity.Name} JOINED");
}
}
public async Task SendMessage(string message)
{
if (this.Context.User?.Identity?.Name != null)
{
await Clients.All.SendAsync(
"ReceiveMessage",
this.Context.User.Identity.Name,
message);
}
}
}
I created an endpoint in my C# code at /events path. If the event is of type "Microsoft.EventGrid.SubscriptionValidationEvent", i extract the ValidationCode and return 200 with a json object with validationResponse. I tested this using Postman, and everything appears to work, but when using "New-AzEventGridSubscription", it fails. Any ideas?
Here's the command I used, scrubbed of course.
New-AzEventGridSubscription -ResourceGroup MyResourceGroupName -Endpoint https://requestb.in/19qlscd1/events -EventSubscriptionName EventSubscription1 -TopicName Topic1
Sample Post:
[
{
"id": "531d4a96-d4c7-43d6-8b4c-e1ff9351b869",
"topic": "scrubbed",
"subject": "",
"data": {
"validationCode": "7062AAC0-656D-4C4C-BDD6-0FA673676D95",
"validationUrl": "scrubbed"
},
"eventType": "Microsoft.EventGrid.SubscriptionValidationEvent",
"eventTime": "2020-03-30T17:46:15.1827678Z",
"metadataVersion": "1",
"dataVersion": "2"
}
]
Sample Response:
{
"validationResponse": "7062AAC0-656D-4C4C-BDD6-0FA673676D95"
}
Here is my code to handle the Event. I've tested this using Ngrok, and it appears to work.
private void HandleEvent(IApplicationBuilder app)
{
app.Run(async context =>
{
string content;
using (var reader = new StreamReader(context.Request.Body))
{
content = reader.ReadToEnd();
}
var eventGridEvents = JsonConvert.DeserializeObject<EventGridEvent[]>(content);
foreach (var eventGridEvent in eventGridEvents)
{
if (eventGridEvent.EventType == "Microsoft.EventGrid.SubscriptionValidationEvent")
{
var eventDataJson = JsonConvert.SerializeObject(eventGridEvent.Data);
var eventData = JsonConvert.DeserializeObject<SubscriptionValidationEventData>(eventDataJson);
var responseData = new SubscriptionValidationResponse()
{
ValidationResponse = eventData.ValidationCode
};
context.Response.StatusCode = 200;
await context.Response.WriteAsync(JsonConvert.SerializeObject(responseData));
}
else
{
var deserialized = JsonConvert.DeserializeObject((string)eventGridEvent.Data);
if (typeof(IEnumerable).IsAssignableFrom(deserialized.GetType()))
{
eventGridEvent.Data = (IEnumerable<object>)deserialized;
}
else
{
eventGridEvent.Data = new List<object>() { deserialized };
}
await HandleMessageListAsync(eventGridEvents.ToList());
}
}
context.Response.StatusCode = 200;
await context.Response.WriteAsync(string.Empty);
});
}
Thank you!
Turns out the issue was my API was returned as "Chunked". Microsoft applied a patch to support "Chunked" responses, and now it works. :)
after login in IdentityServer4 i can't see permisions checkbox and redirection after clicking allow button doesn't work.
So i can see Personal Information title and Application Access title and no checkbox to them.
I'm using asp.net core 2.0 and IdentityServer4
My configuration looks like that:
public class InMemoryConfiguration
{
public static IEnumerable<ApiResource> ApiResources()
{
return new[]
{
new ApiResource("checklist")
};
}
public static IEnumerable<IdentityResource> IdentityResources()
{
return new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
};
}
public static IEnumerable<Client> Clients()
{
return new[]
{
new Client
{
ClientId = "checklist",
ClientSecrets = new [] {new Secret("secret".Sha256())},
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
AllowedScopes = new [] {
"checklist"
}
},
new Client
{
ClientId = "checklist_implicit",
ClientSecrets = new [] {new Secret("secret".Sha256())},
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
AllowedScopes = new [] {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"checklist"
},
RedirectUris = new [] {
"http://localhost:4200/callback"
},
PostLogoutRedirectUris = {"http://localhost:4200/home"},
//RequireConsent=false
}
};
}
public static IEnumerable<TestUser> Users()
{
return new[]
{
new TestUser
{
SubjectId = "1",
Username = "kuba#wp.pl",
Password = "password",
Claims = new [] {new Claim("email", "kuba#wp.pl")}
}
};
}
}
And startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => {
// this defines a CORS policy called "default"
options.AddPolicy("default", policy => {
policy
.SetIsOriginAllowedToAllowWildcardSubdomains()
.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
services.AddIdentityServer()
//.AddSigningCredential(new X509Certificate2(#"D:\kuba\git\checklist\certs\IdentityServer4Auth.pfx", "changeit"))
.AddDeveloperSigningCredential()
.AddTestUsers(InMemoryConfiguration.Users().ToList())
.AddInMemoryClients(InMemoryConfiguration.Clients())
.AddInMemoryIdentityResources(InMemoryConfiguration.IdentityResources())
.AddInMemoryApiResources(InMemoryConfiguration.ApiResources());
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
app.UseDeveloperExceptionPage();
app.UseCors("default");
app.UseIdentityServer();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
On client side i'm using angular
No clue why, but in the Consent view - index.html,
Change this:
<partial name="_ScopeListItem" model="#scope" />
To:
#Html.Partial("_ScopeListItem", scope)
And the consent checkboxes show up as expected.
I have been fiddling around with IDS 4 and I have a small problem. I set my token life times to about 15 seconds and even though they are expired I can still retrieve date from my resource server. If I remove the token from my header on the client call I get a 401 error.
Client
[Authorize]
public async Task<ActionResult> Shouts()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var tokenh = new JwtSecurityTokenHandler();
var jwtSecurityToken= tokenh.ReadJwtToken(accessToken);
var val = jwtSecurityToken.ValidTo;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var rawResponse = await client.GetAsync("http://localhost:5002/api/Values/Get");
if (rawResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
var refreshSucc = await this.RefreshTokensAsync(this.HttpContext);
if (!refreshSucc)
{
var authServerInfo = await this.GetAuthenticationServerInfo();
return Redirect(authServerInfo.AuthorizeEndpoint);
}
}
var response = await (rawResponse).Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<List<String>>(response);
return View("Shouts", data);
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies",o=>o.LogoutPath="/home/logout")
.AddOpenIdConnect("oidc", opt =>
{
opt.SignInScheme = "Cookies";
opt.Authority = "http://localhost:5000";
opt.RequireHttpsMetadata = false;
opt.ClientId = "AuthTest_Code";
opt.ClientSecret = "secret";
opt.ResponseType = "id_token code";
opt.Scope.Add("TestAPI");
opt.Scope.Add("offline_access");
opt.Scope.Add("email");
opt.GetClaimsFromUserInfoEndpoint = true;
opt.SaveTokens = true;
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Auth Server
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "AuthTest_Code",
ClientSecrets=new []{new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Hybrid,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"TestAPI"
},
AllowAccessTokensViaBrowser=true,
AllowOfflineAccess=true,
RedirectUris = new [] { "http://localhost:5001/signin-oidc" },
PostLogoutRedirectUris={ "http://localhost:5001/signout-callback-odic"},
RequireConsent=false,
AccessTokenLifetime=15,
AbsoluteRefreshTokenLifetime=15
}
};
}
public class Startup
{
public IHostingEnvironment Environment { get; }
public Startup(IHostingEnvironment environment)
{
Environment = environment;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
var isServer = services.AddIdentityServer()
.AddSigningCredential(new X509Certificate2(#"C:\OpenSSLCerts\Authenticate.pfx", "Password1"))
.AddInMemoryApiResources(TestConfig.GetApis())
.AddInMemoryClients(TestConfig.GetClients())
.AddInMemoryIdentityResources(TestConfig.GetIdentityResources())
.AddTestUsers(TestConfig.GetUsers());
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseIdentityServer();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
Resource Server
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace ResourceAPI
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "TestAPI";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
}
}
[Route("api/[controller]")]
public class ValuesController:ControllerBase
{
[HttpGet]
[Route("Get")]
[Authorize]
public async Task<IActionResult> Get()
{
var username = User.Claims.FirstOrDefault(t => t.Type == "email")?.Value;
return Ok(new string[] { "value1", "value2" });
}
}
Access Token:
eyJhbGciOiJSUzI1NiIsImtpZCI6IjQ0Q0Q4NjFEQjA0MzdEMUM4NUY2REU0MTIyMkFDOEIwMTE3M0Q2MTAiLCJ0eXAiOiJKV1QiLCJ4NXQiOiJSTTJHSGJCRGZSeUY5dDVCSWlySXNCRnoxaEEifQ.eyJuYmYiOjE1NDIxMjc4OTQsImV4cCI6MTU0MjEyNzkwOSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJUZXN0QVBJIl0sImNsaWVudF9pZCI6IkF1dGhUZXN0X0NvZGUiLCJzdWIiOiIxIiwiYXV0aF90aW1lIjoxNTQyMTI3ODkzLCJpZHAiOiJsb2NhbCIsImVtYWlsIjoiRGF2aWRAQUJldHRlckxpZmUuY28uemEiLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJUZXN0QVBJIiwib2ZmbGluZV9hY2Nlc3MiXSwiYW1yIjpbInB3ZCJdfQ.hNskjZz3IBqPg_5T0xVwYEP5RukcMt3l019PRp74vNBkiEr6FLvBADa_eylhNGA8qDd7SwyDkq6APaKxaNt0YybAChZvFW6pzLlfknVVHM1vuN7PDOX9PNGhFK9kSONBypXo6JqV3epactsmJvhr3FZxBSufUDRyV4j_YY3O8TCjJf_5UORc_3ox9clNdjt-Vx-BlcDjVbpBw2P76F3pq2IPPDM139H4qYcyaTzSlEbFd3EdVwO6O85bWM1G8yQ9zQAUk23It29oHxTtwsi5Wg4Zpmt7K7AlvKjKxKoxw_Y32QdBkGY9xU_KXOn4h0WJV3LG-InZc7BveLGHq6ncaQ
Try setting options.JwtValidationClockSkew property to zero in your web api(resource server)
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.JwtValidationClockSkew = TimeSpan.Zero;
options.Authority = "https://localhost:44323";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
There is a clock skew in the Microsoft JWT validation middleware. It is set by default to 5 mins. And it's suggest to leave it as default (300 seconds/5 minutes).
This is most likely due to the default 5 minute (if memory serves) clock skew allowance. It is however possible to override this setting in IDS4 and the bearer token middleware.
I am testing OWIN middleware and wrote the following middleware class. But strangely, I see that this middleware is called about four times in total when I request a URL without any path (localhost:<port>). However when I call localhost:<port>/welcome.htm or when I call localhost:<port>/default.htm it is only called once as expected. What am I missing?
public class MyMiddleware : OwinMiddleware
{
public MyMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await this.Next.Invoke(context);
if (context.Request.Path.HasValue && (context.Request.Path.Value == "/" || context.Request.Path.Value.EndsWith(".htm")))
{
using (var writer = new StreamWriter(context.Response.Body))
{
await writer.WriteLineAsync("Next middleware: " + this.Next.ToString());
}
}
}
}
and here's my Startup configuration:
public void Configuration(IAppBuilder appBuilder)
{
appBuilder.UseErrorPage(new ErrorPageOptions {
ShowCookies = true,
ShowEnvironment = true,
ShowExceptionDetails = true,
ShowHeaders = true,
ShowQuery = true,
ShowSourceCode = true,
SourceCodeLineCount = 2
});
appBuilder.Use<MyMiddleware>();
appBuilder.UseWelcomePage("/welcome.htm");
appBuilder.UseStaticFiles();
}