IdentityServer4: Is it possible to change Authority during runtime? - identityserver4

In my RP, I define the authority in the ConfigureServices method which is called at startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost:5000";
options.ClientId = "MyClient";
options.ResponseType = "code id_token";
// some more config
});
}
Is it possible to change the authority later?
Use case: The IdP might be restarted, now listening on a different port or even on a different machine. The RP can find the correct address by asking a service discovery, but can I tell the middleware about the new location of the Authority?

Related

How to set cookie expiration times for identity server, web api and client app?

I am new to identity server and know very little about cookie management.
I have configured a web api and a client app with IDS4. below is my startup.cs in identity server.
public void ConfigureServices(IServiceCollection services)
{
//...
var builder = services.AddIdentityServer(options =>
{
options.EmitStaticAudienceClaim = true;
options.IssuerUri = "https://localhost:5001";
})
//...
}
and here is my extension method to add authentication in web api
public static void AddCustomAuthentication(this IServiceCollection services, IConfiguration Configuration)
{
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
// tell the system base address of identity server
options.Authority = Configuration["IdentityServerUri"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
and here is my extension method to add authentication in client app
public static void AddCustomAuthentication(this IServiceCollection services, IConfiguration Configuration)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.Cookie.Name = "CorpLense.HR.WebClient";
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Configuration["IdentityServerUri"];
options.SignInScheme = "Cookies";
options.ClientId = "CorpLense.HR.WebClient";
options.ClientSecret = "***";
options.ResponseType = "code";
options.SaveTokens = true;
// get claims
// request access to scopes
});
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
}
and below is a screenshot of the cookies
OBSERVATIONS
I have noticed that my client app stays logged-in all the time. it does not ask me to login even the next day. however, after a every few hours my web API throws UNAUTHORIZED response codes. And when I logout and login again from my client app, the api starts working fine again. some thing tells me that perhaps the bearer token gets expired.
OBJECTIVE
I just want to know how to have total control on cookies. I want the client app to automatically logout when the cookie on the server side has expired, or the cookie on web api side has expired, or the cookie on the client app has expired.
I also want to know how to control cookie expirations times. and how to refresh bearer tokens if they expire.
If I am not wrong, the default lifetime for the access token is 1 hour and you need to implement support for refresh token to renew the token when the access token is about to expire.
You can also configure the client cookie lifetime in AddCookie(...).
Does this help you?
see https://docs.duendesoftware.com/identityserver/v5/bff/extensibility/tokens/

CORS Error when React App tries to connect via SignalR to .NET CORE Web App having Windows Authentication enabled

I am trying to send data from React App to .NET Core Web App using SignalR.
The .NET Core Web App has Windows Authentication enabled. With Windows Authentication enabled, I am getting the CORS error when my React App tries to send message to .NET Core App via SignalR.
It works fine if I disable windows authentication and enable anonymous authentication.
Can you please help me with your valuable inputs to make the connection work?
React JS app code looks like below:
const hubConnection = new HubConnectionBuilder()
.withUrl(window.config.APP_URL, options)
.withAutomaticReconnect()
.build();
this.setState({ hubConnection }, () => {
this.state.hubConnection
.start()
.then(() => console.log("SignalR Connection started!"))
.catch((err) =>
console.log("SignalR Error while establishing connection :(", err)
);
});
}
sendMessage = () => {
console.log("sendMessage() Properties: ", this.props);
const signalRMessage = {
UserName: this.props.userName,
};
this.state.hubConnection
.invoke("SendMessage", signalRMessage)
.catch((err) => console.error(err));
};
I tried to explicitly add the ‘Access-Control-Allow-Origin’ header as shown below. But still I see the CORS error.
componentDidMount() {
let options = {
httpClient: {
post: (url, httpOptions) => {
// httpOptions.headers = {
// ...httpOptions.headers,
// "Access-Control-Allow-Origin": window.config.CORS_ALLOW_ORIGIN_URL,
// //"Access-Control-Allow-Methods": "POST, GET, HEAD",
// };
// httpOptions.method = "POST";
// httpOptions.url = url;
// return httpOptions;
const headers = {
...httpOptions.headers,
"Access-Control-Allow-Origin": window.config.CORS_ALLOW_ORIGIN_URL,
"Access-Control-Allow-Methods": "POST, GET, HEAD",
};
let newResponse = {};
return axios.post(url, {}, { headers }).then(response => {
return (newResponse = {
statusCode: response.status,
statusText: response.statusText,
content: JSON.stringify(response.data)
});
});
}
}
};
Below is the CORS error that I see in console logs at React App side:
enter image description here
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddRazorPages();
services.AddCors(options =>
{
options.AddPolicy("ClientPermissionPolicy", policy =>
{
policy.WithOrigins("https://xxxx.com")
.AllowAnyHeader()
.AllowAnyMethod().AllowCredentials();
});
});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors("ClientPermissionPolicy");
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<TapHub>("/hubs/tap");
});
}
Went through many posts here in stackoverflow and tried below but in vain.
In Configure():
app.UseCors(builder => builder.WithOrigins("https://xxxx.com").AllowAnyMethod().AllowAnyHeader().AllowCredentials());
In ConfigureServices():
services.AddCors(options =>
{
options.AddPolicy("ClientPermissionPolicy", policy =>
{
policy.WithOrigins("https://xxxxx.com")
.AllowAnyHeader()
.AllowAnyMethod().AllowCredentials()
.SetIsOriginAllowed((host) => true);
});
});
Tried AllowAnyOrigin()
Tried removing AllowCredentials()
Like I said above, it works fine if I disable windows authentication and enable anonymous authentication. The React App successfully connects to the hub endpoint in case of anonymous authentication. The CORS error comes into picture only when I enable windows authentication. I need Windows Authentication enabled for my requirement. Requesting you to help fix the issue.
Thanks!
A bit late, however I faced similar problem right now. Even with enabled CORS, it still rejected me. In the end, I found out that problem is in the
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
When I included CORS into this part, it started to work ... finally
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireCors("ClientPermissionPolicy");
});
You have to place UseCors between UseRouting and UseAutorization
app.UseRouting();
app.UseCors("ClientPermissionPolicy");
app.UseAuthorization();
and maybe you can try to move AddCors to the top of ConfigureServices method
Just the test pourposes I would use this
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("ClientPermissionPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
.....
}
only if it works, I would try to use specific origins.
For those using .NET Core 3.1, here is a COMPLETE solution (front-end to back-end):
My problem: When I enabled the windows authentication on my web API, I could not do fetch calls from my react app to my .NET Core 3.1 web API, CORS was freaking out. With Anonymous authentication it worked, but not when windows authentication is enabled.
1.launchSettings.json
this will be used only for your dev environnment, make sure windows auth is also enabled in IIS on your prod server.
{
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:58747",
"sslPort": 0
}
},
{... more settings if any}
}
2.Startup.cs:
CORS policy is enabled here. The order of methods is important here. Also, you don't need to set those in a web.config
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", //give it the name you want
builder =>
{
builder.WithOrigins( "http://localhost:3000", //dev site
"production web site"
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
//database services here
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
// global policy same name as in the ConfigureServices()
app.UseCors("CorsPolicy");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
3.Controller(s):
using Microsoft.AspNetCore.Cors;
... your other usings
namespace ProjectTest.Controllers
{
[ApiController]
[EnableCors("CorsPolicy")] //THIS HERE needs to be the same name as set in your startup.cs
[Route("[controller]")]
public class FooController:Controller
{
[HttpGet("getTest")]
public JsonResult GetTest()
{
return Json("bar");
}
}
}
4.React Component fetch call example:
The "credential: 'include'" is the secret
await fetch('http://localhost:3000/Foo/getTest', {
method: 'GET',
credentials: 'include'
}).then(resp => resp.json());
As mentioned here, the problem takes place because:
IIS's Windows Auth happens before ASP.NET Core runs, the ASP.NET Core
CORS implementation isn't able to process the OPTIONS request (because
the browser won't send your Windows credentials with it). The only
real solution here is to use the IIS CORS module since it runs before
Windows Authentication and can process the unauthenticated CORS
request.

Razor Pages Application not redirecting to IdentityServer4

I am having trouble getting my .NET Core site to redirect to my identity server for authentication when accessing a page. When I run the site locally this works fine. However, when I deploy the site it no longer works.
Here is the code for the application startup (just for the authentication)
public void ConfigureServices(IserviceCollection services) {
services
.AddAuthentication(options => {
options.DefaultScheme => "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options => {
options.Authority = "IdentityServerUrl";
options.ClientId = "clientId";
options.ResponseType = "code";
options.SaveTokens = true;
options.Scope.Add("scope");
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapRazorPages().RequireAuthorization());
}
And then in our page:
public class IndexModel : PageModel {
public async Task OnGetAsync() {
var authentication = await this.HttpContext.AuthenticateAsync();
var accessTokenJwt = authentication.Properties.Items[".Token.access_token"];
}
}
This is all working correctly when run locally. It is successfully redirecting to the identity server, logging in, returning to the application, and setting the cookies.
However, when this is deployed to a webserver it is not working at all. The await this.HttpContext.AuthenticateAsync(); is called and just immediately returns null. This results in an exception being thrown on the line below.
Any help with this would be much appreciated. Thanks in advance.

Identity Server 4 with Azure AD - "We couldn't sign you in. Please try again."

I'm using .NET Core 3.1 with Identity Server 4 and connecting to Azure AD via OpenIdConnect. I'm using a Vue.js front-end and .NET Core API. IdentityServer, the front-end, and the API are all hosted on-prem on the same server (same domain). Everything uses https. I'm using an Oracle database with EF model first, with fully-customized IdentityServer stores and a custom user store (I implemented the interfaces). I'm using IdentityServer's Quickstart, edited a little to hook up my custom user store instead of the test user. I'm running this in my dev environment.
If I type in the url to the IdentityServer, I'm redirected to Azure AD, signed-in successfully, and shown this page:
Grants - successful login
The claims are coming back from Azure AD and the auto-provisioning is successful. It is written successfully to the database.
Authenticating through my JS client hits IdentityServer, redirects to Azure AD, I sign-in, then it redirects to IdentityServer's ExternalController, then redirects back to a Microsoft url, then proceeds to repeat until it finally fails with this page:
Sign-in failure from Azure AD
My guess is I messed up a redirect uri somewhere. Here is my code and the IdentityServer log:
IdentityServer Log
That block of logging repeats 6-10 times. No errors or anything different at the end.
I had to break up the C# code because the site couldn't handle one of my long options lines.
IdentityServer Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Logout";
options.Authentication = new AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromHours(10),
CookieSlidingExpiration = true
};
}).AddClientStore<ClientStore>()
.AddCorsPolicyService<CorsPolicyService>()
.AddResourceStore<ResourceStore>()
.AddPersistedGrantStore<PersistedGrantStore>()
.AddProfileService<UserProfileService>();
services.AddScoped<IUserStore, UserStore>();
if (env.IsDevelopment())
{
// not recommended for production
builder.AddDeveloperSigningCredential();
}
else
{
// TODO: Load Signing Credentials for Production.
}
services.AddAuthentication()
.AddOpenIdConnect("aad", "Azure AD", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "https://login.windows.net/[authority]";
options.CallbackPath = "/callback-aad";
options.ClientId = "[ClientId]";
options.RemoteSignOutPath = "/signout-aad";
options.RequireHttpsMetadata = true;
options.ResponseType = OpenIdConnectResponseType.IdToken;
options.SaveTokens = true;
options.SignedOutCallbackPath = "/signout-callback-aad";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
options.UsePkce = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseStaticFiles();
app.UseSerilogRequestLogging();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Client OIDC config:
const oidcSettings = {
authority: '[IdentityServerUrl]',
client_id: '[ClientId]',
post_logout_redirect_uri: '[front-end url]/logout-aad',
redirect_uri: '[front-end url]/callback-aad',
response_type: 'code',
save_tokens: true,
scope: 'openid profile',
}
Callback method being hit for ExternalController:
[HttpGet]
public async Task<IActionResult> Callback()
{
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
if (_logger.IsEnabled(LogLevel.Debug))
{
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {#claims}", externalClaims);
}
// lookup our user and external provider info
var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
if (user == null)
{
// this might be where you might initiate a custom workflow for user registration
// in this sample we don't show how that would be done, as our sample implementation
// simply auto-provisions new external user
user = await AutoProvisionUser(provider, providerUserId, claims);
}
// this allows us to collect any additional claims or properties
// for the specific protocols used and store them in the local auth cookie.
// this is typically used to store data needed for signout from those protocols.
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallback(result, additionalLocalClaims, localSignInProps);
// issue authentication cookie for user
var isuser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await HttpContext.SignInAsync(isuser, localSignInProps);
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// retrieve return URL
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
// check if external login is in the context of an OIDC request
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId));
if (context != null)
{
if (context.IsNativeClient())
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
return this.LoadingPage("Redirect", returnUrl);
}
}
return Redirect(returnUrl);
}
Azure AD config:
redirect uri: [IdentityServer url]/callback-aad
Database table data:
Client table IMG1
Client table IMG2
ClientScopes table
ClientRedirectUris table
Please let me know if you need any additional information. Thank you
The problem was in my custom UserStore. I was getting the user by the Azure AD SubjectId instead of the UserSubjectId. So in the ExternalController, the ApplicationUser object was coming up as null. Instead of an exception, it kept going back to Azure AD to try to get the user again, but obviously that just creates an infinite loop. I didn't think to look there since my user was successfully provisioned with Id's and claims.

Infinite authentication loop when using identityserver4 in asp.net core 2.0

I have an Identity Server using identityserver4 framework, its url is http://localhost:9000
My web application is asp.net core 2.0, its url is http://localhost:60002. This application will use the login page of Identity Server.
I want after logging in, the Identity Server will redirect to the application page (http://localhost:60002)
Here is the Startup.cs of client application
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
private string AuthorityUri => Configuration.GetValue<string>("UserManagement-Authority");
// 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 = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = AuthorityUri; // "http://localhost:9000"
options.RequireHttpsMetadata = false;
options.ClientId = "customer.api";
options.ClientSecret = "testsecret";
options.ResponseType = "code id_token";
options.Scope.Add("customerprivatelinesvn.api");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.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.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
Here is the loggin page on Identity Server
But there is an infinite loop that calls to http://localhost:9000/connect/authorize endpoint, and then it returns to http://localhost:60002/signin-oidc with "Bad Request - Request Too Long" as below.
When I look at the cookies, there ar lots of items ".AspNetCore.Correlation.OpenIdConnect.xxx"
Here is the log on Identiy Server. It said that Identiy.Application was successfully authenticated.
Does anyone know what this problem is? And how to resolve this? Thank you very much.
Best regards,
Kevin
I also had a login loop after copying the startup code from an existing .NET Core 2.2 project and reused it in a new .NET Core 3.1 project.
The problem here was, that the app.UseAuthentication() must be called before the new app.UseAuthorization();
https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#migrate-startupconfigure
Only in case someone is running into this issue too...
Adding default Identity in the client app would cause an infinite redirect loop.
In the client app, if you need to use UserManager, RoleManager.
Then use the below code.
services.AddIdentityCore<IdentityUser>()
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddSignInManager<SignInManager<IdentityUser>>()
.AddEntityFrameworkStores<ApplicationDbContext>();
In your client app, in Startup check if you have something like
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Remove that part and try again.
In my case, I was missing RedirectUri when initiating the Signin from the client. Problem solved by adding the RedirectUri as below.
public IActionResult SignIn()
{
return Challenge(new AuthenticationProperties() { RedirectUri = "/" }, "oidc" );
}
Well, you do have a very long request shown there in your Identity Server log - and the error says "Bad Request - request too long". I'd guess that the problem is that your request is too big :)
maximum length of HTTP GET request?
Have you tried posting rather than using a GET?
This issue was solved after I updated the latest nuget package of IdentityServer4 and .NET Core.

Resources