aspnet boilerplate UseJWTTokenMiddleware with Identityserver4 - automapper

I updated the existing boilerplate application to the latest version but I am facing issues in the authentication when using JWT after user login I am receiving the below error
An unhandled exception occurred while processing the request.
AutoMapperMappingException: Missing type map configuration or
unsupported mapping.
Mapping types: Object -> PersistedGrantEntity System.Object ->
Abp.IdentityServer4.PersistedGrantEntity
My Configuration in startup like
services.AddIdentityServer(options =>
{
options.IssuerUri = _appConfiguration["IdentityServer:baseUrl"];
//options.PublicOrigin = options.IssuerUri;
})
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
.AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
.AddInMemoryApiScopes(IdentityServerConfig.GetApiScopes())
.AddInMemoryClients(IdentityServerConfig.GetClients(_appConfiguration))
.AddAbpPersistedGrants<IAbpPersistedGrantDbContext>()
.AddAbpIdentityServer<User>();
services.AddAuthentication().AddIdentityServerAuthentication("IdentityBearer",
options =>
{
options.Authority = options.Authority = >
_appConfiguration["IdentityServer:baseUrl"];
options.RequireHttpsMetadata = false;
});

Related

Using Header propagation middleware (Microsoft.AspNetCore.HeaderPropagation) in a Function App

I am currently working on a Timer-Triggered Isolated Process Function App targeting .Net 7.0, and am having some difficulties getting Header Propogation configured.
I followed the code example here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-7.0#header-propagation-middleware
but couldn't see where to add the 'app.UseHeaderPropagation();' in my Function App program.cs class.
My code so far:
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices((hostContext, services) => {
services.AddAccessTokenManagement(options =>
{
options.Client.Clients.Add("identityserver", new ClientCredentialsTokenRequest
{
Address = PropertyRetrieve.GetPropertyValue("AccessTokenUrl"),
ClientId = PropertyRetrieve.GetPropertyValue("ClientID"),
ClientSecret = PropertyRetrieve.GetPropertyValue("ClientSecret"),
Scope = PropertyRetrieve.GetPropertyValue("Scope")
}) ;
}).ConfigureBackchannelHttpClient();
services.AddHeaderPropagation(options =>
{
options.Headers.Add("ETag", "If-Match");
});
services.AddHttpClient("default", c =>
{
c.DefaultRequestHeaders.Add("User-Agent", "Coverys_XuberSync");
c.DefaultRequestHeaders.Add("Accept", "*/*");
c.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
c.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
c.DefaultRequestHeaders.Add("Host", PropertyRetrieve.GetPropertyValue("XuberApi")[8..]);
c.DefaultRequestHeaders.Add("Connection", "keep-alive");
c.BaseAddress = new Uri(PropertyRetrieve.GetPropertyValue("XuberApi"));
}).AddClientAccessTokenHandler("identityserver")
.AddHeaderPropagation();
})
.Build();
host.Run();
When I run this I get the following message:
Executed 'Functions.Function1' (Failed, Id=9a9731b1-83aa-48d3-8c04-a8df658f46f6, Duration=6963ms)
[2023-02-17T11:14:03.976Z] System.Private.CoreLib: Exception while executing function: Functions.Function1. System.Private.CoreLib: Result: Failure
Exception: System.AggregateException: One or more errors occurred. (The HeaderPropagationValues.Headers property has not been initialized. Register the header propagation middleware by adding 'app.UseHeaderPropagation()' in the 'Configure(...)' method. Header propagation can only be used within the context of an HTTP request.)
Please could you assist with initialising this middleware in a Function App.
I have tried to Use the Middleware in the 'ConfigureFunctionsWorkerDefaults' function, but this does not work
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(builder =>
{
builder.UseMiddleware<HeaderPropagationMiddleware>();
})
Error Message:
CS0311: The type 'MicrosoftAspNetCoreHeaderPropagationHeaderPropagationMiddleware' cannot be used as type parameter 'T in the generic type or method
'MiddlewareWorkerApplicationBuilderExtensions.UseMiddleware (IFunctionsWorkerApplicationBuilder)'. There is no implicit reference conversion from
'MicrosoftAspNetCoreHeaderPropagationHeaderPropagationMiddleware' to 'Microsoft.Azure.Functions.Worker.Middleware.lFunctionsWorkerMiddleware'.

Service/Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware] No CORS policy found for the specified request

This might be an already answered question but I have tried already what I have found on stackoverflow.
This is the problem:
No cors policy found, how to fix this?
Situation: I have an identity server 4 server, that allows external authentication (oidc), 2 external servers so far. One works fine the other one (Azure AD B2C) don't.
This is my startup class:
public class Startup
{
public readonly Microsoft.Extensions.Logging.ILogger<IdentityServer4.Services.DefaultCorsPolicyService> logger;
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().AddDapr();
var builder = services.AddIdentityServer(opt => opt.IssuerUri = "http://tenant")
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryApiResources(Config.ApiResources)
.AddInMemoryClients(Config.Clients)
.AddDeveloperSigningCredential();
/*var cors = new IdentityServer4.Services.DefaultCorsPolicyService(logger)
{
AllowAll = true
};*/ //this didn't work
services.AddSingleton<IdentityServer4.Services.ICorsPolicyService>((container) => {
var logger = container.GetRequiredService<Microsoft.Extensions.Logging.ILogger<IdentityServer4.Services.DefaultCorsPolicyService>>();
return new IdentityServer4.Services.DefaultCorsPolicyService(logger)
{
AllowAll = true
};
});
/*services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});*/ //didn't work
/*services.AddCors(options =>
{
options.AddDefaultPolicy(
builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()
);
});*/
AddServices(services);
services.AddOidcStateDataFormatterCache();
AddAuth(services);
AddSwaggerGeneration(services);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors();
if (env.IsDevelopment())
{
app.UseExceptionHandler("/error-local-development");
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Tenant v1"));
}
else
{
app.UseExceptionHandler("/error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseCloudEvents();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
private void AddServices(IServiceCollection services)
{
services.AddSingleton<ITenantRepository, DaprTenantRepository>();
services.AddSingleton<IDaprClientAdaptor, DaprClientAdaptor>();
}
private void AddAuth(IServiceCollection services)
{
services.AddAuthentication()
.AddOpenIdConnect("oidc", "TestIdentityServer", options =>
{
//...code for the first identity provider server, the one that works
})
.AddOpenIdConnect("oidc3", "AzureInternalTestIdentityServer", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.Authority = "https://my azure authority";
options.ClientId = "my clientId";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.Scope.Add("email");
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
services.AddAuthorization();
}
private void AddSwaggerGeneration(IServiceCollection services)
{
services.AddVersionedApiExplorer(options => options.GroupNameFormat = "'v'VVV");
services.AddApiVersioning(options =>{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
services.AddSwaggerGen(c =>
{
c.EnableAnnotations();
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = #"JWT Authorization header using the Bearer scheme.
Enter 'Bearer' [space] and then your token in the text input below.
Example: 'Bearer eyJhbGciOiJSU...'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
});
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
}
}
As you can see, I am using app.UseCors, I have setup Serilog on program.cs, I don't know if that will impact it.
logs from docker (I am using docker and dapr):
2021-07-13T14:07:39.7018825+00:00 [INF][/Service/Microsoft.AspNetCore.Hosting.Diagnostics] Request starting HTTP/2 GET https://localhost:5007/External/Challenge?scheme=oidc3&returnUrl=%2Fdiagnostics - -
2021-07-13T14:07:39.7093231+00:00 [INF][/Service/Microsoft.AspNetCore.Routing.EndpointMiddleware] Executing endpoint 'IdentityServerHost.Quickstart.UI.ExternalController.Challenge (Tenant)'
2021-07-13T14:07:39.7141041+00:00 [INF][/TenantService/Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker] Route matched with {action = "Challenge", controller = "External"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult Challenge(System.String, System.String) on controller IdentityServerHost.Quickstart.UI.ExternalController (Tenant).
2021-07-13T14:07:39.7455289+00:00 [INF][/TenantService/Microsoft.AspNetCore.Mvc.ChallengeResult] Executing ChallengeResult with authentication schemes (["oidc3"]).
2021-07-13T14:07:41.1914398+00:00 [INF][/TenantService/Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler] AuthenticationScheme: oidc3 was challenged.
2021-07-13T14:07:41.2103318+00:00 [INF][/TenantService/Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker] Executed action IdentityServerHost.Quickstart.UI.ExternalController.Challenge (Tenant) in 1496.0595ms
2021-07-13T14:07:41.2115812+00:00 [INF][/TenantService/Microsoft.AspNetCore.Routing.EndpointMiddleware] Executed endpoint 'IdentityServerHost.Quickstart.UI.ExternalController.Challenge (Tenant)'
2021-07-13T14:07:41.2454932+00:00 [INF][/TenantService/Microsoft.AspNetCore.Hosting.Diagnostics] Request finished HTTP/2 GET https://localhost:5007/External/Challenge?scheme=oidc3&returnUrl=%2Fdiagnostics - - - 302 0 - 1543.5567ms
2021-07-13T14:07:41.8513003+00:00 [INF][/TenantService/Microsoft.AspNetCore.Hosting.Diagnostics] Request starting HTTP/2 POST https://localhost:5007/signin-oidc application/x-www-form-urlencoded 427
2021-07-13T14:07:41.8578905+00:00 [INF][/TenantService/Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware] No CORS policy found for the specified request.
2021-07-13T14:07:41.8630414+00:00 [INF][/TenantService/Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware] No CORS policy found for the specified request.
2021-07-13T14:07:42.0976889+00:00 [ERR][/TenantService/Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler] Exception occurred while processing message.
System.Security.Cryptography.CryptographicException: The payload was invalid.
at Microsoft.AspNetCore.DataProtection.Managed.ManagedAuthenticatedEncryptor.Decrypt(ArraySegment`1 protectedPayload, ArraySegment`1 additionalAuthenticatedData)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(Byte[] protectedData, Boolean ignoreRevocationErrors, Boolean& requiresMigration, Boolean& wasRevoked)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData)
at Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.Unprotect(IDataProtector protector, String protectedData)
at IdentityServer4.Infrastructure.DistributedCacheStateDataFormatter.Unprotect(String protectedText, String purpose)
at IdentityServer4.Infrastructure.DistributedCacheStateDataFormatter.Unprotect(String protectedText)
at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.ReadPropertiesAndClearState(OpenIdConnectMessage message)
at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleRemoteAuthenticateAsync()
2021-07-13T14:07:42.6735507+00:00 [INF][/TenantService/Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler] Error from RemoteAuthentication: The payload was invalid..
2021-07-13T14:07:43.1458887+00:00 [ERR][/TenantService/Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware] An unhandled exception has occurred while executing the request.
System.Exception: An error was encountered while handling the remote login.
---> System.Security.Cryptography.CryptographicException: The payload was invalid.
at Microsoft.AspNetCore.DataProtection.Managed.ManagedAuthenticatedEncryptor.Decrypt(ArraySegment`1 protectedPayload, ArraySegment`1 additionalAuthenticatedData)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(Byte[] protectedData, Boolean ignoreRevocationErrors, Boolean& requiresMigration, Boolean& wasRevoked)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData)
at Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.Unprotect(IDataProtector protector, String protectedData)
at IdentityServer4.Infrastructure.DistributedCacheStateDataFormatter.Unprotect(String protectedText, String purpose)
at IdentityServer4.Infrastructure.DistributedCacheStateDataFormatter.Unprotect(String protectedText)
at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.ReadPropertiesAndClearState(OpenIdConnectMessage message)
at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleRemoteAuthenticateAsync()
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
at IdentityServer4.Hosting.FederatedSignOut.AuthenticationRequestHandlerWrapper.HandleRequestAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
2021-07-13T14:07:43.2072371+00:00 [INF][/TenantService/Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware] No CORS policy found for the specified request.
2021-07-13T14:07:43.2084321+00:00 [INF][/TenantService/Microsoft.AspNetCore.Routing.EndpointMiddleware] Executing endpoint 'TenantsService.V1.Controllers.ErrorController.ErrorLocalDevelopment (Tenant)'
2021-07-13T14:07:43.2205122+00:00 [INF][/TenantService/Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker] Route matched with {action = "ErrorLocalDevelopment", controller = "Error"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult ErrorLocalDevelopment(Microsoft.AspNetCore.Hosting.IWebHostEnvironment) on controller TenantsService.V1.Controllers.ErrorController (Tenant).
2021-07-13T14:07:43.2723048+00:00 [INF][/TenantService/Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor] Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.ProblemDetails'.
2021-07-13T14:07:43.3656934+00:00 [INF][/TenantService/Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker] Executed action TenantsService.V1.Controllers.ErrorController.ErrorLocalDevelopment (Tenant) in 145.0361ms
2021-07-13T14:07:43.3658905+00:00 [INF][/TenantService/Microsoft.AspNetCore.Routing.EndpointMiddleware] Executed endpoint 'TenantsService.V1.Controllers.ErrorController.ErrorLocalDevelopment (Tenant)'
2021-07-13T14:07:43.3662553+00:00 [INF][/TenantService/Microsoft.AspNetCore.Hosting.Diagnostics] Request finished HTTP/2 POST https://localhost:5007/signin-oidc application/x-www-form-urlencoded 427 - 500 - application/problem+json;+charset=utf-8 1514.9476ms
I don't believe CORS is required here as the Oauth2/Open ID Connection mainly bounce around using HTTP redirects. and I am not sure exactly when CORS are required in Identity Server, maybe when you want to retrieve user info with the idtoken using ajax?
The thing with Azure AD B2C is that it gets a few additional parameters like sign in /out policy that I believe the normal OpenID Connect middleware cannot handle it well. I will suggest try to use MSAL library(Web.Identity.Web) to implement it. So rather than the AddOpenIdConnect call, you do
services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAdB2C");
as shown here.
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/blob/master/1-WebApp-OIDC/1-5-B2C/Startup.cs
Good luck!

Call ASP .NET Core API in Azure from daemon application

I have limited experience on Azure AD and authentication mechanism. So far I cannot figure out why is not working. Here is the scenario:
I have an ASP net core 2.1 application deployed in azure web app service.
For authentication I’m using Open ID connect with .AddOpenIdConnect and provide client_id, secret_id, etc. When users are accessing my web API they are redirected to Microsoft login.
Now I need to expose an API to a third party application (scheduled web job) which is not in Azure.
I tried to use this sample from Microsoft, only the console app, as I already have the WebApp in Azure.
Running the sample I’m able to get the token, but when I call my API the response is the HTML to Microsoft login page.
On Azure portal on
Enterprise Application -> daemon-console -> Activity -> Service Principal sign-ins
I can see the success sign in.
Note: for testing I run the web app on my local machine and from the console application I’m calling API https://localhost:44306/api/test.
Asp .net core app:
services.AddAuthentication(option =>
{
option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(option =>
{
option.Cookie.Name = "myWebApp";
option.Cookie.SecurePolicy = CookieSecurePolicy.Always;
option.Cookie.SameSite = SameSiteMode.None;
})
.AddOpenIdConnect(option =>
{
option.ClientId = client_id;
option.ClientSecret = client_secret;
option.Authority = authority;
option.SignedOutRedirectUri = "http://localhost:44306/";
option.CorrelationCookie.Name = "myWebAppCorrelation";
option.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
option.NonceCookie.Name = "WebAppNonce";
option.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
option.Resource = "https://graph.windows.net";
option.ResponseType = "id_token code";
})
Console app trying to access the API ( code extracted from Microsoft sample )
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
result = await app.AcquireTokenForClient(scopes).ExecuteAsync(); // ok
var httpClient = new HttpClient();
var defaultRequestHeaders = httpClient.DefaultRequestHeaders;
if (defaultRequestHeaders.Accept == null || !defaultRequestHeaders.Accept.Any(m => m.MediaType == "application/json"))
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync(webApiUrl);
if (response.IsSuccessStatusCode) // ok
{
string json = await response.Content.ReadAsStringAsync(); // here I'm getting the HTML to login page
var result = JsonConvert.DeserializeObject<List<JObject>>(json);
Console.ForegroundColor = ConsoleColor.Gray;
processResult(result);
}
The only difference between the sample code and my scenario is that the web app from sample is using services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(Configuration)
but I cannot use .AddMicrosoftIdentityWebApi in Asp .Net core 2.1
Does anyone has an idea where the issue might be ? Do I need to add another authentication scheme ?
You need to support JWT authentication in addition to cookie authentication. So you need to add AddJwtBearer. You also need to extend the authentication check because you are now supporting multiple schemes.
This is how I would do it:
// public void ConfigureServices(IServiceCollection services)
services.AddAuthentication(option =>
{
option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, cfg => {
cfg.Authority = authority;
cfg.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = /*see scopes of your deamon app*/
};
})
.AddCookie(option =>
{
// ...
})
.AddOpenIdConnect(option =>
{
// ...
})
// public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseAuthentication();
// https://github.com/aspnet/Security/issues/1847
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
var result = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
if (result.Succeeded)
{
context.User = result.Principal;
}
}
await next();
});
app.UseAuthorization();

Microsoft.Identity.Web and ASP.NET Core SignalR JWT authentication

I am using ASP.NET Core to make a web application that also uses SignalR Core to provide real time functionality. I use Azure AD B2C for user management. I have successfully used Microsoft.Identity.Web (https://github.com/AzureAD/microsoft-identity-web) to secure my API endpoints using tokens generated by Azure AD B2C.
I would like to do the same for my SignalR Core hubs. The documentation reads to add the appropriate annotation to your hubs/methods, which I have done. SignalR's client side library adds the access token as a query parameter which must be extracted and added to the context manually in the configuration of your ASP.NET core application, like so.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
However, this seems to be incompatible with the configuration supplied by Microsoft.Identity.Web, here:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C"));
How can I make SignalR work with Microsoft.Identity.Web?
That should do it:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(configuration);
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
Func<MessageReceivedContext, Task> existingOnMessageReceivedHandler = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
await existingOnMessageReceivedHandler(context);
StringValues accessToken = context.Request.Query["access_token"];
PathString path = context.HttpContext.Request.Path;
// If the request is for our hub...
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
{
// Read the token out of the query string
context.Token = accessToken;
}
};
});
Instead of adding a JwtBearer, you can configure the JwtBearerOptions object this way.
Adapted from this documentation: https://github.com/AzureAD/microsoft-identity-web/wiki/customization
You can use Visual Studio to set up the SignalR connection, and then just add this line in startup.cs (VS might add it automatically)
services.AddSignalR().AddAzureSignalR();
This dev sample has SignalR set up, just the connection string is missing, but might give you an idea of what to do. Most of this was done automatically with VS. If you have issues setting it up, please open an issue in the repo. thanks.

Skipping home realm discovery with Ws-Federation OWIN Middleware

Our Mvc/WebAPI solution currently has four trusted identity providers which we have registered in ADFS3. Each of these identity providers can be used by our users by direct links, effectively working around any home-realm-cookies that ADFS may have created (eg: www.ourportal.com/accounts/facebook or www.ourportal.com/accounts/twitter). Currently we are migrating from WIF to OWIN but will keep using WS-Federation protocol for the time being by implementing wsfederation and cookie authentication middleware. When using WIF, we did the following in order to go directly to a known identity provider:
var signInRequest = new SignInRequestMessage(stsUrl, realm) { HomeRealm = homeRealm };
return new RedirectResult(signInRequest.WriteQueryString());
This seems to have two concerning behaviors, it does not pass the WsFedOwinState parameter, and on the return back to the Relying Party, the Home.cshtml is built (with a windows principal) before the the Owin authentication middleware is fired. The Home.cshtml being fired before the Owin middleware is the most concering as this view relies on Claims that would is provided in the transformation done by the authentication pipeline, which is fired afterwards and thus our view does not work. It works in the correct order when going to the portal in the normal way (eg www.ourportal.com)
I understand that in order to provide the Whr parameter, you do the following when configuring the ws-federation middleware:
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.Whr = "SomeUrnOfAnIdentityProvider";
return Task.FromResult(0);
}
but this sets a single identity provider for the whole solution and does not allow our users to go directly to one of a list of identity providers.
The non-working method which builds the sign-in-request is currently:
private RedirectResult FederatedSignInWithHomeRealm(string homeRealm)
{
var stsUrl = new Uri(ConfigurationManager.AppSettings["ida:Issuer"]);
string realm = ConfigurationManager.AppSettings["ida:Audience"];
var signInRequest = new SignInRequestMessage(stsUrl, realm)
{
HomeRealm = homeRealm
};
HttpContext.Request.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return new RedirectResult(signInRequest.WriteQueryString());
}
The ws-federation and cookie middleware are configured as the first middleware in OWIN startup and the default authentication is set to
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
I think I found a solution. The new method for skipping the home realm screen would be like this :
private void FederatedSignInWithHomeRealm(string homeRealm)
{
HttpContext.Request
.GetOwinContext()
.Authentication
.SignOut(CookieAuthenticationDefaults.AuthenticationType);
var authenticationProperties = new AuthenticationProperties { RedirectUri = "/" };
authenticationProperties.Dictionary.Add("DirectlyToIdentityProvider", homeRealm);
HttpContext.GetOwinContext().Authentication.Challenge(authenticationProperties);
}
And the OWIN WS-Federation middleware would be configured like this :
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = notification =>
{
string homeRealmId = null;
var authenticationResponseChallenge = notification.OwinContext
.Authentication
.AuthenticationResponseChallenge;
var setIdentityProvider = authenticationResponseChallenge != null
&& authenticationResponseChallenge.Properties
.Dictionary
.TryGetValue("DirectlyToIdentityProvider", out homeRealmId);
if (setIdentityProvider)
{
notification.ProtocolMessage.Whr = homeRealmId;
}
return Task.FromResult(0);
}
},
MetadataAddress = wsFedMetadata,
Wtrealm = realm,
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = realm
}
});

Resources