I have an application gateway set up ("gateway"):
apps.mydomain.com
I have an app service set up ("app"):
my-app-service.azurewebsites.net
The path based rule is set on the listener for on the gateway address above.
/apps/app1/*
The default backend target and settings are set to the root of the gateway address above.
I am using AADS as the authentication store.
Both work correctly independently as I have another route set up on the gateway. I can go to the app service and it will prompt me for credentials, then take me to the index page at the root.
my-app-service.azurewebsites.net/
What I am trying to do is set up a path based rule that routes through the gateway and lands on a path under apps.mydomain.com. For example,
apps.mydomain.com/apps/app1.
I have set up the gateway properly as I can get to a static page. For example,
apps.mydomain.com/apps/app1/somedirectory/mystaticpage.html.
My problem is that when I try to authenticate, I think the signin-oidc is routing the request incorrectly. I am able to authenticate, and it appears to pass back to apps.mydomain.com/apps/app1/signin-oidc and then the middleware passes back to the root. It is authenticating, because when it hits the error page, it shows me as logged in.
I have tried overriding the cookie policy options:
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.Secure = CookieSecurePolicy.SameAsRequest;
options.MinimumSameSitePolicy = SameSiteMode.None;
//options.HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.None;
});
I have tried listening to the OnRedirectToIdentityProvider:
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
//options.CallbackPath = new PathString("/apps/app1/");
//options.CallbackPath = new PathString("/apps/app1/signin-oidc");
//options.CallbackPath = "/apps/app1/signin-oidc";
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = (context) =>
{
//https://stackoverflow.com/questions/50262561/correlation-failed-in-net-core-asp-net-identity-openid-connect
context.Options.NonceCookie.Path = "https://apps.mydomain.com/apps/app1/signin-oidc";
context.Options.CorrelationCookie.Path = "https://apps.mydomain.com/apps/app1/signin-oidc";
//https://learn.microsoft.com/en-us/azure/frontdoor/front-door-http-headers-protocol#front-door-to-backend
context.ProtocolMessage.RedirectUri = "https://apps.mydomain.com/apps/app1/signin-oidc";
return Task.FromResult(0);
}
};
});
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
My guess is that just setting the otions.CallbackPath should work, but I just get correlation or sorry, we cannot log you in errors when I try that. Not sure if there is an error in the library.
I have spent over a month on and off and engaged MS technical support trying to solve this, but have not been able to get this to work. I can't imagine I am the only one doing this. I know it is in the open ID connect middleware somewhere, but cannot find the correct combination.
This is just a demo project in .NET 6 to get this working correctly. Any code will do. If there is actual working code somewhere that would be great. Just need to get the path based routing with authentication to work.
I have a ASP.NET 5 WebApp that is part of a bigger system and uses Cookie Authentication for Browser requests.
I want to add the ability to request data and perform specific actions on certain Windows services that are also part of the overall system and are executed on a couple of seperate PCs. I want to use SignalR for this.
Then Windows-Services are running as a dedicated service identity that is part of our ActiveDirectory. Since the services shall not store their user credentials in code or local configuration files, they are requesting an authentication token for the web application from an API that works with Windows Authentication.
Then, when establishing the SignalR connection with the web app, the services will use the token received from the API to authenticate against the web app. This is working in general.
The Authentication configuration of the web app is:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Login";
options.ExpireTimeSpan = TimeSpan.FromMinutes(12);
options.SlidingExpiration = true;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opt =>
{
// Configuration details excluded
// ...
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// ...
}
};
According to Microsoft Documentation this should be a vaild authentication configuration.
In services.AddAuthorization(...) method I've added a policy specific for Bearer scheme:
options.AddPolicy("SignalRService", policy =>
{
policy.RequireRole("SignalRService");
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
});
And then there is a SignalR Hub Method secured with this policy:
[Authorize(Policy = "SignalRService")]
public async Task RegisterService(string clientIdString) { /*...*/ }
And finally the hub connection in the windows service is created as follows:
connection = new HubConnectionBuilder()
.WithUrl(hubAddress, options =>
{
options.AccessTokenProvider = () => Task.FromResult(authToken);
})
.WithAutomaticReconnect()
.Build();
Establishing the connection works:
await connection.StartAsync();
But when I try to call the hub method from the windows service like await connection.InvokeAsync("RegisterService", clientId); I receive a HubException with the message:
Failed to invoke 'RegisterService' because user is unauthorized
I have also created an API Controller on the web app for testing purposes and secured it with the same policy:
[HttpGet]
[Authorize(Policy = "SignalRService")]
public IActionResult Get()
{
return Ok(User.Identity.Name);
}
When I call this API endpoint with the same token i would user for SignalR Hub call, I get the identity set on the token returned as expected. I also verified that the configured OnMessageReceived event handler is executed in this scenario, while it isn't when I use SignalR connection.
When I set JwtBearerDefaults.AuthenticationScheme as the default scheme in Startup.cs instead of CookieAuthenticationDefaults.AuthenticationScheme it works also with the SignalR Hub, but then my standard Cookie based user authenticaton breaks.
I expect that there is some additonal configuration necessary to tell the web app to explicitely use the Bearer scheme when a Hub method is called, but I could not find anything so far.
After desperately trying for another hour, I found out that the specific bearer authentication worked with Cookie authentication as the default, when I put the Authorize(Policy = "SignalRService") directly on the class instead of on the method.
Since my hub should also be accessible for browser connections using cookies, I finally ended up with:
[Authorize(AuthenticationSchemes = "Bearer,Cookies")]
public class SignalRServiceHub : Hub
{
[Authorize(Policy = "SignalRService")]
public async Task RegisterService(string clientIdString)
{
// ...
}
[Authorize(Policy = "Root")]
public async Task RegisterMonitoringClient()
{
// ...
}
I'm not exactly sure why specifying the Schemes on class level is necessary in this case while it isn't for ApiController implementations
I am using .net 5, Identity Web Ui to access Microsoft Graph. Where can I configure my Redirect URI?
I need to specify the full Uri, since the generated one from callbackUri is incorrect due to being behind a Load Balancer with SSL offload.
Here is my current ConfigureServices section
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
I was facing a similar problem with a WebApp exposed only behind a front door, the WebApp had to call a custom downstream WebApi.
My service configuration that worked on my localhost dev machine:
// AzureAdB2C
services
.AddMicrosoftIdentityWebAppAuthentication(
Configuration,
"AzureAdB2C", subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true)
.EnableTokenAcquisitionToCallDownstreamApi(p =>
{
p.RedirectUri = redUri; // NOT WORKING, WHY?
p.EnablePiiLogging = true;
},
[... an array with my needed scopes]
)
.AddInMemoryTokenCaches();
I tried the AddDownstreamWebApi but did not manage to make it work so I just fetched the needed token with ITokenAcquisition and added it to an HttpClient to make my request.
Then I needed AzureAd/B2C login redirect to the uri with the front door url:
https://example.org/signin-oidc and things broke. I solved it like this:
First of all you have to add this url to your App registration in the azure portal, very important is case sensitive it cares about trailing slashes and I suspect having many urls that point to the very same controller and the order of these have some impact, I just removed everything and kept the bare minimum.
Then in the configure services method:
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SaveTokens = true; // this saves the token for the downstream api
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = async ctxt =>
{
// Invoked before redirecting to the identity provider to authenticate. This can be used to set ProtocolMessage.State
// that will be persisted through the authentication process. The ProtocolMessage can also be used to add or customize
// parameters sent to the identity provider.
ctxt.ProtocolMessage.RedirectUri = "https://example.org/signin-oidc";
await Task.Yield();
}
};
});
With that the redirect worked, but I entered a loop between the protected page and the AzureB2C login.
After a succesful login and a correct redirect to the signin-oidc controller (created by the Identity.Web package) I was correctly redirected again to the page that started all this authorization thing, but there it did not find the authorization. So I added/modded also this:
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
options.Secure = CookieSecurePolicy.Always;
});
With this the authorization worked, but I was not able to get the token to call the downstream API, before this redirect thing ITokenAcquisition worked, but now when trying to get the token it throws an exception.
So in my controller/service to get the token I modified and used:
var accessToken = await _contextAccessor.HttpContext
.GetTokenAsync(OpenIdConnectDefaults.AuthenticationScheme, "access_token");
So now with the token I add it to my HttpRequestMessage like this:
request.Headers.Add("Authorization", $"Bearer {accessToken}");
I lived on StackOverflow and microsoft docs for 3 days, I am not sure this is all "recommended" but this worked for me.
I had the same problem running an asp.net application under Google Cloud Run, which terminates the TLS connection. I was getting the error:
AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application.
Using fiddler, I examined the request to login.microsoftonline.com and found that the query parameter redirect_uri exactly matched the url I'd configured in the application in Azure except that it started http rather than https.
I initially tried the other answers involving handling the OpenIdConnectEvents event and updating the redirect uri. This fixed the redirect_url parameter in the call to login.microsoftonline.com and it then worked until I added in the graph api. Then I found my site's signin-oidc page would give its own error about the redirect uri not matching. This would then cause it to go into a loop between my site and login.microsoftonline.com repeatedly trying to authenticate until eventually I'd get a login failure.
On further research ASP.net provides middleware to properly handle this scenario. Your SSL load balancer should add the standard header X-Forwarded-Proto with value HTTPS to the request. It should also send the X-Forwarded-For header with the originating IP address which could be useful for debugging, geoip etc.
In your ASP.net application, to configure the middleware:
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
Then enable the middleware:
app.UseForwardedHeaders();
Importantly, you must include this before the calls to app.UseAuthentication/app.UseAuthorization that depends on it.
Source: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-5.0
If your load balancer doesn't add the X-Forwarded-Proto header and can't be configured to do so then the document above outlines other options.
I was facing with similar issue for 3 days. The below code helped me to get out of the issue.
string[] initialScopes = Configuration.GetValue<string>("CallApi:ScopeForAccessToken")?.Split(' ');
services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddInMemoryTokenCaches();
services.AddControllers();
services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser().Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SaveTokens = true; // this saves the token for the downstream api
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = async ctxt =>
{
ctxt.ProtocolMessage.RedirectUri = "https://example.org/signin-oidc";
await Task.Yield();
}
};
});
I am developing an Authentication server to deliver tokens to be used to consume our API using IdentityServer4.
I am using MongoDB as database on which I have the users allowed to get tokens, and to be more secure I am using a custom certificate to encrypt the token.
This is how my Startup.cs of my AuthenticationServer looks like:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "cert", "whatever.pfx"), "whatever");
services.AddIdentityServer().AddSigningCredential(cert)
.AddInMemoryApiResources(Config.Config.GetApiResources());
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<IClientStore, ClientStore>();
services.AddTransient<IProfileService, UserProfileService>();
services.AddTransient<IResourceOwnerPasswordValidator, UserResourceOwnerPasswordValidator>();
services.AddTransient<IPasswordHasher<User.Model.User>, PasswordHasher<User.Model.User>>();
}
As you can see I have custom implementation of those interfaces that do the client authentication and password validation. This is working fine.
Then I am protecting another application with the tokens generated, and I have there defined that it has to use the IdentityServerAuthetication (localhost:5020 is where my AuthenticationServer is running)
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors("CorsPolicy");
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5020",
RequireHttpsMetadata = false,
ApiName = "MyAPI",
RoleClaimType = JwtClaimTypes.Role
});
app.UseMvc();
}
All works fine, but if I shutdown the AuthenticationServer then I get this error from the API I am protecting:
System.InvalidOperationException: IDX10803: Unable to obtain configuration from: 'http://localhost:5020/.well-known/openid-configuration'.
at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.d__24.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.d__1.MoveNext()
fail: Microsoft.AspNetCore.Server.Kestrel[13]
So what it seems is the API is going to the discovery endpoint to see where is the endpoint to decrypt the token (I guess it would be userinfo_endpoint).
My point is:
It seems that the discovery endpoint is used to get information of how to use the authentication server (for me makes sense for open APIs), but in our case we are not developing and open API, so our clients would be just the ones we have agreement and we will tell them in advance the endpoints and we will most likely restrict by IP address.
Is there any way to deactivate the discovery endpoint and to setup on the API the certificate to properly decrypt the token.
Maybe I am missing the complete picture and I am saying silly things, but I would be glad to understand the concepts behind.
Thanks in advance
It is possible to have the discovery endpoint off/unavailable and still validate tokens.
You'll need to implement IConfigurationManager and pass that to a JwtBearerOptions object inside of IdentityServerAuthenticationOptions.
Here's some example code:
public class OidcConfigurationManager : IConfigurationManager<OpenIdConnectConfiguration>
{
public OidcConfigurationManager()
{
SetConfiguration();
}
private OpenIdConnectConfiguration _config;
public Task<OpenIdConnectConfiguration> GetConfigurationAsync(CancellationToken cancel)
{
return Task.FromResult<OpenIdConnectConfiguration>(_config);
}
public void RequestRefresh()
{
}
private void SetConfiguration()
{
// Build config from JSON
var configJson =
#"{""issuer"":""http://localhost/id"",""jwks_uri"":""http://localhost/id/.well-known/openid-configuration/jwks"",""authorization_endpoint"":""http://localhost/id/connect/authorize"",""token_endpoint"":""http://localhost/id/connect/token"",""userinfo_endpoint"":""http://localhost/id/connect/userinfo"",""end_session_endpoint"":""http://localhost/id/connect/endsession"",""check_session_iframe"":""http://localhost/id/connect/checksession"",""revocation_endpoint"":""http://localhost/id/connect/revocation"",""introspection_endpoint"":""http://localhost/id/connect/introspect"",""frontchannel_logout_supported"":true,""frontchannel_logout_session_supported"":true,""scopes_supported"":[""openid"",""profile"",""api1"",""offline_access""],""claims_supported"":[""sub"",""name"",""family_name"",""given_name"",""middle_name"",""nickname"",""preferred_username"",""profile"",""picture"",""website"",""gender"",""birthdate"",""zoneinfo"",""locale"",""updated_at""],""grant_types_supported"":[""authorization_code"",""client_credentials"",""refresh_token"",""implicit"",""password""],""response_types_supported"":[""code"",""token"",""id_token"",""id_token token"",""code id_token"",""code token"",""code id_token token""],""response_modes_supported"":[""form_post"",""query"",""fragment""],""token_endpoint_auth_methods_supported"":[""client_secret_basic"",""client_secret_post""],""subject_types_supported"":[""public""],""id_token_signing_alg_values_supported"":[""RS256""],""code_challenge_methods_supported"":[""plain"",""S256""]}";
_config = new OpenIdConnectConfiguration(configJson);
// Add signing keys if not present in json above
_config.SigningKeys.Add(new X509SecurityKey(cert));
}
}
Now pass that config object to some JwtBearerOptions inside of your IdentityServerAuthenticationOptions (a bit annoying, but it's the only way I know of)
var identityServerOptions = new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5020",
RequireHttpsMetadata = false,
ApiName = "MyAPI",
RoleClaimType = JwtClaimTypes.Role,
};
var jwtBearerOptions = new JwtBearerOptions() {ConfigurationManager = new OidcConfigurationManager()};
var combinedOptions = CombinedAuthenticationOptions.FromIdentityServerAuthenticationOptions(identityServerOptions);
combinedOptions.JwtBearerOptions = jwtBearerOptions;
app.UseIdentityServerAuthentication(combinedOptions);
Now your API will be able to receive tokens and validate the signatures even if the OIDC discovery endpoint is off.
The API validation middleware downloads a copy of the discovery document at startup - and then (at least by default) - every 24h.
It might re-trigger download if signature validation fails (to accommodate for out of schedule key rollovers).
You can define all configuration values statically - but you are losing all the benefits of dynamic configuration updates.
If your discovery endpoint is not available, then probably the whole token service is not functional which is probably a bigger problem.
The IdentityServer needs the public key of your X509 certificate to validate the access_token. It is using the discovery endpoint to get that public key, and is refreshing the saved public key every now and then (because the public key could change).
If the IdentityServer is not accessible, your API can't guarantee that the access_token is valid. You can increase the caching of the results of the access_token validation requests.
I'm not entirely sure with IdentityServer4.AccessTokenValidation, but with IdentityServer3.AccessTokenValidation, you can set the ValidationMode to Local only, so it just downloads the public key once.
I've been tasked with trying to move our signalR hub to an azure cloud service with a service bus backplane. No problems there. The javascript client is able to get the hubs.js and connect without errors. We also have a web api project that needs to send messages to the hub but I cannot get it to connect. Everything I've tried doesn't work and the connection times out. I must be missing something but, since this is my first time working with signalR and Azure, I don't know what it is. The web api is hosted on IIS.
Here is the code I am trying to use to connect:
private async void InitializeHub()
{
string connectionString = "http://xxxx-xxxxx.cloudapp.net/signalr";
var hubConnection = new HubConnection(connectionString, useDefaultUrl: false);
//var hubConnection = new HubConnection(connectionString);
HubProxy = hubConnection.CreateHubProxy("clientPortalHub");
await hubConnection.Start();
}
Is there some configuration I am missing? Anything need to be done in IIS? I'm not getting any helpful error messages, just that the connection times out. I can hit the url (the real one, not the one I pasted) in a browser and get "Unknown transport".
If it helps here is the startup from the hub:
public void Configuration(IAppBuilder app)
{
// Any connection or hub wire up and configuration should go here
string connectionString = "<omitted>";
GlobalHost.DependencyResolver.UseServiceBus(connectionString, "clientPortalHub");
app.Map("/signalr", map =>
{
map. UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
hubConfiguration.EnableDetailedErrors = true;
map.RunSignalR(hubConfiguration);
});
}