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

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.

Related

HTTP Error 500 when deploying ASP.NET Core 2.2 to additional Azure Web App

I have already deployed the application to a Azure web app that works just fine. I am using an ASP.NET Core 2.2 application with MVC connected to a SQL server located on Azure. I am working with dotnet SDK version 3.1.301.
The application has a React client. I am trying to make the application multi - tenancy, therefore, each web app has its own Azure Key Vault but a single database. All of the environment variables are located within the key vault, including the connection string to the db.
I have created an additional web app on Azure (this one is for production) connected to an additional Key Vault. After deploying to this web app, I get a HTTP 500 error. In the web activity logs I do not receive any errors. I am using Microsoft Visual 2019 to deploy the applications.
My question is; I have followed all the same steps for the first deployment and I would really like to know why, when spinning up a new app with a new key vault, why I am not getting any errors?
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
if (context.HostingEnvironment.IsProduction())
{
var builtConfig = config.Build();
var keyVaultUri = builtConfig.GetValue<string>("KEY_VAULT_URI"); //Connection to KEY VAULT
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(keyVaultUri, keyVaultClient, new DefaultKeyVaultSecretManager());
}
}
).UseStartup<Startup>();
}
Startup.cs
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)
{
services.AddTransient<ILearnerService, LearnerService>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(options =>
// Fixed Unexpected end of JSON
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services.AddDbContext<DataContext>(
option => option.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddAutoMapper();
services.AddCors();
// Configure DI for application services
services.AddScoped<ILearnerService, LearnerService>();
// In production, the React files will be served from this directory **********
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
// Configure strongly typed settings objects
var appSettingsSection = Configuration;
services.Configure<AppSettings>(appSettingsSection);
services.AddMultiTenancy()
.WithResolutionStrategy<HostResolutionStrategy>()
.WithStore<InMemoryTenantStore>();
// Configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
var encryptionKey = Encoding.UTF8.GetBytes(appSettings.EncryptionSecret);
services.AddAuthorization(options =>
{
options.AddPolicy(TenantFeatures.Inbox, policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser()
.RequireAssertion(context =>
context.User.HasClaim(TenantFeatures.Inbox, "user.Stores.Companies"))
.Build();
});
});
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var learnerService = context.HttpContext.RequestServices.GetRequiredService<ILearnerService>();
var userId = int.Parse(context.Principal.Identity.Name);
var user = learnerService.GetById(userId);
if (user == null)
{
// Return unauthorized if user no longer exists
context.Fail("Unauthorized");
}
// user.Stores.Companies
var tenantInfo = Configuration.GetValue<string>("tesco-training-net");
if (tenantInfo.Contains(TenantFeatures.Inbox))
{
Claim claim = new Claim(TenantFeatures.Inbox, "user.Stores.Companies");
((ClaimsIdentity)context.Principal.Identity).AddClaim(claim);
}
if (tenantInfo.Contains(TenantFeatures.DynamicCV))
{
Claim claim = new Claim(TenantFeatures.DynamicCV, "user.Stores.Companies");
((ClaimsIdentity)context.Principal.Identity).AddClaim(claim);
}
return Task.CompletedTask;
}
};
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
RequireSignedTokens = true,
// Fixed [Authorize] decrypts the tokens
TokenDecryptionKey = new SymmetricSecurityKey(encryptionKey),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
}
// 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();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthentication();
app.UseMultiTenancy();
app.UseMvc();
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
EDIT
Within the Application logs, I am getting this:
IIS was not able to access the web.config file for the Web site or
application. This can occur if the NTFS permissions are set incorrectly.
IIS was not able to process configuration for the Web site or application.
The authenticated user does not have permission to use this DLL. The request is mapped to a managed handler but the .NET Extensibility Feature is not installed. Things you can try: Ensure that the NTFS permissions for the web.config file are correct and allow access to the Web server's machine account. Check the event logs to see if any additional information was logged. Verify the permissions for the DLL. Install the .NET Extensibility feature if the request is mapped to a managed handler. Create a tracing rule to track failed requests for this HTTP status code. For more information about creating a tracing rule for failed requests, click here.

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.

Pass JWT token from angular HttpClient to access secure .NET Core Web API

I need to call secure Web API from Angular 9 application by presenting the token. I am using Angular with .NET CORE 3.1 Web API. I have managed to generate Azure B2C token but stuck to call secure web api as I got CORS error.
Angular component calling Web API end-point
testAPI1(){
console.log("calling test API ...");
const myheaders = new HttpHeaders({
'Content-Type': 'application/json; charset=utf-8',
'Authorization': this.authService.accessToken
});
this.http.get('https://localhost:5001/txn/v1/Dashboard/GetMessage', {headers: myheaders})
.subscribe((data)=>{
console.warn(data);
})
}
Auth Service
#Injectable()
export class AuthService implements OnInit{
constructor(
private oauthService: OAuthService,
private router: Router
){// other code}
public get accessToken() {
return this.oauthService.getAccessToken();
}
Web API controller & endpoint
[Authorize]
[Route("txn/v1/[controller]/[action]")]
[EnableCors("CorsPolicy")]
[ApiController]
public class DashboardController : ControllerBase
{
[HttpGet]
public ActionResult<HelloMessage> GetMessage()
{
var result = new HelloMessage()
{
GivenName = "james",
ReturnMessage = "Dashboard# Hello, Welcome to Digital tech"
};
return result;
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//JWT Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(jwtConfig =>
{
jwtConfig.Audience = Configuration["AzureAdB2C:ResourceId"];
jwtConfig.Authority = $"{Configuration["AzureAdB2C:Instance"]}{Configuration["AzureAdB2C:TanantId"]}";
jwtConfig.RequireHttpsMetadata = false;
jwtConfig.SaveToken = true;
jwtConfig.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer =true,
ValidateAudience = true,
ValidateLifetime = true
};
});
//CORS policy
services.AddCors(options =>
options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()));
error
Policies for CORS can be a bit finicky. So I would recommend maybe trying for a pretty open CORS policy (Which isn't too dangerous given you are using header authentication and not a cookie).
So your configure services method should look like so :
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
}
And then your Configure method should be something like :
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors( options => options.WithOrigins("http://example.com").AllowAnyMethod() );
app.UseMvc();
}
Note that the order inside the Configure method is important. The call to CORS must be relatively early on, if not the first middleware in your pipeline.
If that works, then work backwards to slowly add policies and see which one breaks. CORS can be really finicky so it works better to allow everything in a basic example and then add thing slowly in.
More reading here : https://dotnetcoretutorials.com/2017/01/03/enabling-cors-asp-net-core/

SignalR Hub not receiving user from client WPF (.netcore 3.1)

I have a WPF client which connects successfully to a Hub, but I cannot pass the user of the client to the Hub.
My connection.User?.Identity?.Name in my class implementing from IUserIdProvider returns null.
For my WPF client I use this to connect against the Hub:
_connection = new HubConnectionBuilder()
.WithUrl(viewModel.Endpoint, opts =>
{
opts.Credentials = new NetworkCredential("user", "password", "domain");
opts.UseDefaultCredentials = true;
})
.Build();
I have then the following provider registered as singleton:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
As I mentioned above, the connection.User?.Identity?.Name; is returning null.
I don't know what else I can do to pass the user name from my client (WPF) to my Hub.
By the way, my Startup.cs looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddLogging();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
services.AddSignalR(hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
});
services.AddScoped<IBuildsService, BuildsService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<SyncCodeHub>("/signalr");
});
}
Any help would be much appreciated.
EDIT:
I update the code with:
services.AddAuthentication(IISDefaults.AuthenticationScheme);
But the problem continues, the identity user (IUserIdProvider) is returning null when called from the WPF client. I'm running the API locally with IISExpress.
EDIT:
From Microsoft docs:
Windows Authentication is only supported by the browser client when using Microsoft Internet Explorer or Microsoft Edge.
So I'm wondering if this is even possible with an Desktop as a client. I assume it should work, so I'm wondering if I'm still missing a point or if this is a bug related to the Version of SignalR I#m using (3.1.3)
You need to configure your ASP.NET Core app to use Windows authentication by calling AddAuthentication in the ConfigureServices method of the Startup class:
services.AddAuthentication(IISDefaults.AuthenticationScheme);
You should also edit your launchSettings.json file according to the docs:
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:52171/",
"sslPort": 44308
}
}

401 Unauthorized errors when accessing WebApI from AngularJS/ADAL.js client

I've got a self-hosted web api application with an angular front end, and I need to now start authenticating users via Azure Active Directory.
I've downloaded the SinglePageApp example and I've set this up and have it running successfully.
https://github.com/Azure-Samples/active-directory-angularjs-singlepageapp-dotnet-webapi
When applying the necessary changes to my own app, I can successfully redirect the user to the Azure login screen and get back the userProfile using adal.js/adal_angular.js. I'm getting 401 unauthorized errors whenever I call my API, however using Fiddler, I can see that the bearer token is added to the HTTP header in each call.
Here is my AdalAngular setup:
.config(["$httpProvider", "adalAuthenticationServiceProvider", ($httpProvider, adalProvider) => {
adalProvider.init(
{
instance: "https://login.microsoftonline.com/",
tenant: "<snip>.onmicrosoft.com",
clientId: "<snip>",
extraQueryParameter: "nux=1",
cacheLocation: "localStorage" // enable this for IE, as sessionStorage does not work for localhost.
},
$httpProvider);
Here is my startup.cs code:
public void Configuration(IAppBuilder appBuilder)
{
ConfigureWebApi(appBuilder);
ConfigureAuth(appBuilder);
ConfigureFileSystem(appBuilder);
appBuilder.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
}
private void ConfigureWebApi(IAppBuilder appBuilder)
{
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
private void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ActiveDirectoryTenant"],
Audience = ConfigurationManager.AppSettings["ActiveDirectoryApplicationId"]
});
}
private void ConfigureFileSystem(IAppBuilder appBuilder)
{
//Set the Welcome page to test if Owin is hosted properly
appBuilder.UseWelcomePage("/welcome.html");
appBuilder.UseErrorPage(new Microsoft.Owin.Diagnostics.ErrorPageOptions() { ShowExceptionDetails = true });
var physicalFileSystem = new PhysicalFileSystem(#".\wwwroot");
if (ConfigurationManager.AppSettings.AllKeys.Contains("ContentPath"))
{
var path = ConfigurationManager.AppSettings["ContentPath"];
physicalFileSystem = new PhysicalFileSystem(path);
}
FileServerOptions fileOptions = new FileServerOptions();
fileOptions.EnableDefaultFiles = true;
fileOptions.RequestPath = PathString.Empty;
fileOptions.FileSystem = physicalFileSystem;
fileOptions.DefaultFilesOptions.DefaultFileNames = new[] { "index.html" };
fileOptions.StaticFileOptions.FileSystem = fileOptions.FileSystem = physicalFileSystem;
fileOptions.StaticFileOptions.ServeUnknownFileTypes = true;
appBuilder.UseFileServer(fileOptions);
}
Where ActiveDirectoryTenant and ActiveDirectoryApplicationId are in my app.config and match what is configured in my angular adalProvider.init code exactly.
Finally, my ApiController looks like this:
[Authorize]
[RoutePrefix("api/connection")]
public class ServerConnectionController : ApiController
{
[Route("all")]
[HttpGet]
public HttpResponseMessage GetAllConnections()
{
HttpResponseMessage response;
try
{
string owner = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var connections = _iDataAccess.GetAllConnections().ToList();
response = Request.CreateResponse(HttpStatusCode.OK, connections);
}
catch (Exception ex)
{
response = GetExceptionResponseMessage(ex);
}
return response;
}
}
As mentioned the HTTP request header captured by Fiddler looks ok, and the aud property on my ADAL.js userInfo.profile is the correct appid.
Any suggestions on what might be missing?
Note that this is not a native web based app, it's self-hosted, which means the web service is running on localhost as a windows service, and not in IIS.
I have configured the site to use HTTPS, but I get the same problem regardless of HTTP or HTTPS traffic.
Thanks for listening!
You need to declare the ConfigureAuth(appBuilder); as the first line in the Startup.cs Configuration method. You can find a good explanation here on why it need to be declared as the first.

Resources