Is there a way of using SignalR with webpack-dev-proxy? I think I tried every possible solution but had no success. I am using react-native and for local development, we can use webpack dev server to get around CORS.
I didn't specify any transport type so by default SignalR is trying all available transport types hoping one of them will work.
logs:
SignalR client:
const signalR = new signalRBuilder.HubConnectionBuilder()
.withUrl(prefixUrl('/log'))
.configureLogging(LogLevel.Trace)
.withAutomaticReconnect()
.build();
webpack.config.js
const createExpoWebpackConfigAsync = require('#expo/webpack-config');
const constants = require('./constants');
function proxy(urls) {
return urls.reduce((acc, v) => Object.assign(acc, acc, {
[v]: {
target: constants.API_BASE_URL,
changeOrigin: true,
logLevel: 'debug',
ws: true,
},
}), {});
}
// Expo CLI will await this method, so you can optionally return a promise.
// eslint-disable-next-line func-names
module.exports = async function (env, argv) {
const config = await createExpoWebpackConfigAsync(env, argv);
// Maybe you want to turn off compression in dev mode.
if (config.mode === 'development') {
config.devServer.proxy = proxy(['/api', '/log']);
config.devServer.clientLogLevel = 'info';
config.devServer.compress = false;
}
// Or prevent minimizing the bundle when you build.
if (config.mode === 'production') {
config.optimization.minimize = false;
}
// Finally return the new config for the CLI to use.
return config;
};
Startup.cs
namespace API
{
public class Startup
{
private readonly IConfigurationRoot _configuration;
private readonly IWebHostEnvironment _env;
public Startup(IWebHostEnvironment env)
{
_env = env;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true)
.AddJsonFile("secureHeaderSettings.json", true, true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public void ConfigureServices(IServiceCollection services)
{
// If environment is localhost, then enable CORS policy, otherwise no cross-origin access
services.AddCors(options => options.AddPolicy("CorsPolicy", builder => builder
.WithOrigins(_configuration.GetSection("TrustedSpaUrls").Get<string[]>())
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()));
// https://stackoverflow.com/a/70304966/1834787
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
var coldStartConfig = new BlobContainerClient(new Uri(Environment.GetEnvironmentVariable("AZURE_BLOB_CONFIG")!))
.GetBlobClient("cold-start-config");
services.AddDataProtection()
.SetApplicationName("milwaukee-internationals-website-cold-start")
.PersistKeysToAzureBlobStorage(coldStartConfig)
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
services.AddWebMarkupMin()
.AddHtmlMinification()
.AddXmlMinification()
.AddHttpCompression();
services.AddOptions();
// Add our Config object so it can be injected
services.Configure<SecureHeadersMiddlewareConfiguration>(
_configuration.GetSection("SecureHeadersMiddlewareConfiguration"));
services.AddLogging();
services.Configure<JwtSettings>(_configuration.GetSection("JwtSettings"));
// Add MailKit
services.AddMailKit(optionBuilder =>
{
var emailSection = _configuration.GetSection("Email");
var mailKitOptions = new MailKitOptions
{
// Get options from secrets.json
Server = emailSection.GetValue<string>("Server"),
Port = emailSection.GetValue<int>("Port"),
SenderName = emailSection.GetValue<string>("SenderName"),
SenderEmail = emailSection.GetValue<string>("SenderEmail"),
// Can be optional with no authentication
Account = emailSection.GetValue<string>("Account"),
Password = Environment.GetEnvironmentVariable("EMAIL_PASSWORD"),
// Enable ssl or tls
Security = true
};
optionBuilder.UseMailKit(mailKitOptions);
});
services.AddRouting(options =>
{
options.LowercaseUrls = true;
});
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromMinutes(50);
options.Cookie.HttpOnly = true;
options.Cookie.Name = ApiConstants.AuthenticationSessionCookieName;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Milwaukee-Internationals-API", Version = "v1" });
});
services.AddMvc(x =>
{
x.ModelValidatorProviders.Clear();
// Not need to have https
x.RequireHttpsPermanent = false;
// Allow anonymous for localhost
if (_env.IsDevelopment())
{
x.Filters.Add<AllowAnonymousFilter>();
}
x.Filters.Add<PreventAuthenticatedActionFilter>();
})
.AddViewOptions(x => { x.HtmlHelperOptions.ClientValidationEnabled = true; })
.AddNewtonsoftJson(x =>
{
x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
x.SerializerSettings.Converters.Add(new StringEnumConverter());
}).AddRazorPagesOptions(x => { x.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()); });
services.AddWebMarkupMin(opt =>
{
opt.AllowMinificationInDevelopmentEnvironment = true;
opt.AllowCompressionInDevelopmentEnvironment = true;
})
.AddHtmlMinification()
.AddHttpCompression();
services.Scan(scan => scan
.FromAssemblies(Assembly.Load("API"), Assembly.Load("Logic"), Assembly.Load("DAL"))
.AddClasses() // to register
.UsingRegistrationStrategy(RegistrationStrategy.Skip) // 2. Define how to handle duplicates
.AsImplementedInterfaces() // 2. Specify which services they are registered as
.WithTransientLifetime()); // 3. Set the lifetime for the services
services.AddSingleton<CacheBustingUtility>();
// If environment is localhost then use mock email service
if (_env.IsDevelopment())
{
services.AddSingleton<IEmailServiceApi>(new EmailServiceApi());
}
else
{
/*
var (accessKeyId, secretAccessKey, url) = (
_configuration.GetRequiredValue<string>("CLOUDCUBE_ACCESS_KEY_ID"),
_configuration.GetRequiredValue<string>("CLOUDCUBE_SECRET_ACCESS_KEY"),
_configuration.GetRequiredValue<string>("CLOUDCUBE_URL")
);
var prefix = new Uri(url).Segments[1];
const string bucketName = "cloud-cube";
// Generally bad practice
var credentials = new BasicAWSCredentials(accessKeyId, secretAccessKey);
*/
// Create S3 client
/*services.AddSingleton<IAmazonS3>(ctx => new AmazonS3Client(credentials, RegionEndpoint.USEast1));
services.AddSingleton(new S3ServiceConfig(bucketName, prefix));
services.AddTransient<IStorageService>(ctx => new S3StorageService(
ctx.GetRequiredService<ILogger<S3StorageService>>(),
ctx.GetRequiredService<IAmazonS3>(),
ctx.GetRequiredService<S3ServiceConfig>()
));*/
services.AddSingleton(new BlobContainerClient(new Uri(Environment.GetEnvironmentVariable("AZURE_BLOB_CONFIG")!)));
services.AddTransient<IStorageService, AzureBlobService>();
}
services.AddSingleton<GlobalConfigs>();
// Initialize the email jet client
services.AddTransient<IMailjetClient>(ctx => new MailjetClient(
Environment.GetEnvironmentVariable("MAIL_JET_KEY"),
Environment.GetEnvironmentVariable("MAIL_JET_SECRET"))
);
services.AddDbContext<EntityDbContext>(opt =>
{
if (_env.IsDevelopment())
{
opt.EnableDetailedErrors();
opt.EnableSensitiveDataLogging();
opt.UseSqlite(_configuration.GetValue<string>("ConnectionStrings:Sqlite"));
}
else
{
var postgresConnectionString =
ConnectionStringUrlToPgResource(_configuration.GetValue<string>("DATABASE_URL")
?? throw new Exception("DATABASE_URL is null"));
opt.UseNpgsql(postgresConnectionString);
}
});
services.AddIdentity<User, IdentityRole<int>>(x =>
{
x.User.RequireUniqueEmail = true;
x.Lockout.AllowedForNewUsers = true;
x.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(2);
x.Lockout.MaxFailedAccessAttempts = 3;
})
.AddEntityFrameworkStores<EntityDbContext>()
.AddRoles<IdentityRole<int>>()
.AddDefaultTokenProviders();
var jwtSetting = _configuration
.GetSection("JwtSettings")
.Get<JwtSettings>();
// Random JWT key
jwtSetting.Key = PasswordGenerator.Generate(length: 100, allowed: Sets.Alphanumerics);
services.AddSingleton(jwtSetting);
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(x =>
{
x.Cookie.MaxAge = TimeSpan.FromHours(3);
x.LoginPath = new PathString("/Identity/login");
x.LogoutPath = new PathString("/Identity/logout");
}).AddJwtBearer(config =>
{
config.RequireHttpsMetadata = false;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = jwtSetting.Issuer,
ValidAudience = jwtSetting.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.Key))
};
});
services.AddEfRepository<EntityDbContext>(opt => opt.Profile(Assembly.Load("Dal")));
services.AddSingleton(new TableServiceClient(new Uri(Environment.GetEnvironmentVariable("AZURE_TABLE_EVENTS")!)));
services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
services.AddSignalR(config =>
{
config.MaximumReceiveMessageSize = 10 * 1024 * 1024; // 10 mega-bytes
config.StreamBufferCapacity = 50;
config.EnableDetailedErrors = true;
config.HandshakeTimeout = TimeSpan.MaxValue;
}).AddNewtonsoftJsonProtocol();
services.AddAutoMapper(Assembly.Load("Models"));
}
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="configLogic"></param>
/// <param name="apiEventService"></param>
public void Configure(IApplicationBuilder app, IConfigLogic configLogic, IApiEventService apiEventService)
{
// Refresh global config
configLogic.Refresh();
// Add SecureHeadersMiddleware to the pipeline
app.UseSecureHeadersMiddleware(_configuration.Get<SecureHeadersMiddlewareConfiguration>());
app.UseCors("CorsPolicy");
if (_env.IsDevelopment())
{
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); });
}
else
{
app.UseWebMarkupMin();
}
// Not necessary for this workshop but useful when running behind kubernetes
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
// Read and use headers coming from reverse proxy: X-Forwarded-For X-Forwarded-Proto
// This is particularly important so that HttpContent.Request.Scheme will be correct behind a SSL terminating proxy
ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto
});
app.Use(async (context, next) =>
{
await next();
if (context.Response.IsFailure())
{
var exHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
var exception = exHandlerFeature?.Error;
var statusCodeEnum = (HttpStatusCode)context.Response.StatusCode;
await apiEventService.RecordEvent($"Failure with status code: {statusCodeEnum.ToString()} / {context.Response.StatusCode} route: [{context.Request.Method}] {context.Request.GetDisplayUrl()} => {exception?.Message}");
context.Request.Path = $"/Error/{context.Response.StatusCode}";
await next();
}
});
// Use wwwroot folder as default static path
app.UseDefaultFiles()
.UseHttpsRedirection()
.UseEnableRequestRewind()
.UseStaticFiles()
.UseCookiePolicy()
.UseSession()
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<MessageHub>("/hub");
endpoints.MapHub<LogHub>("/log");
});
Console.WriteLine("Application Started!");
}
}
}
React-native project:
repository
To run: npm run web
.NET web application
Related
I am currently developing an application using Asp.net core 5.0 and Identity server 4.My OIDC authentication flow handled by Microsoft.AspNetCore.Authentication.OpenIdConnect. I deployed my application into IIS and I am getting my login screen. But after login I got the Http 400 Bad request error.Error. I checked my application cookie it contains many AspNetCore.OpenIdConnect.Nonce cookies. I deleted the cookies but doesn't solve my issue. What is the proper solution to handle this solution. I tried this but doesn't help me. I will share the code below
MVC Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddScoped<RenewToken>();
services.AddAuthorization(options =>
{
options.AddPolicy("CreatePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RC", "UC")));
options.AddPolicy("ReadPolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RR", "UR")));
});
services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.Cookie.Name = "Cookies";
options.ExpireTimeSpan = TimeSpan.FromMinutes(10);
options.SlidingExpiration = true;
}).AddOpenIdConnect("oidc", options =>
{
options.BackchannelHttpHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = delegate { return true; } };
options.Authority = Configuration.GetSection("API:IDS4").Value;
options.SignInScheme = "Cookies";
options.SignedOutRedirectUri = Configuration.GetSection("API:WebClient").Value + "/signout-callback-oidc";
options.RequireHttpsMetadata = true;
options.ClientId = "mvc";
options.ClientSecret = "*****";
options.ResponseType = "code";
options.UsePkce = true;
options.Scope.Add("profile");
options.Scope.Add("mcApi");
options.Scope.Add("Api1");
options.Scope.Add("Api2");
options.Scope.Add("Api3");
options.Scope.Add("Api4");
options.Scope.Add("Api5");
options.Scope.Add("Api6");
options.Scope.Add("Api7");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Events.OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Prompt = "login";
return Task.CompletedTask;
};
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = (context) =>
{
context.Response.Redirect("/");
context.HandleResponse();
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = JwtClaimTypes.Role
};
});
services.AddHttpClient<IAdminService, AdminService>();
services.AddSingleton<DataProtectionPurposeStrings>();
services.AddSingleton<GlobalConstants>();
}
private bool AuthorizeAccess(AuthorizationHandlerContext context, string roleClaim, string userClaim)
{
return context.User.HasClaim(claim => claim.Type == roleClaim && claim.Value == "True") &&
context.User.HasClaim(claim => claim.Type == userClaim && claim.Value == "True") ||
context.User.IsInRole("MyAdmin");
}
// 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();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Identity Server Startup.cs
public class Startup
{
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
/****Register asp.net core Identity DBConetexts***/
var idenConnectionString = Configuration["DbContextSettings:IdentityConnectionString"];
var dbPassword = Configuration["DbContextSettings:DbPassword"];
var builder = new NpgsqlConnectionStringBuilder(idenConnectionString)
{
Password = dbPassword
};
services.AddDbContext<IdentityDBContext>(opts => opts.UseNpgsql(builder.ConnectionString));
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Password.RequiredLength = 8;
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+ ";
options.SignIn.RequireConfirmedEmail = false;
options.User.RequireUniqueEmail = false;
}).AddRoles<ApplicationRole>().AddEntityFrameworkStores<IdentityDBContext>()
.AddDefaultTokenProviders();
/****Identity Server implementation with asp.net core Identity***/
var idsServerConnectionString = Configuration["DbContextSettings:IdentityServer4ConnectionString"];
var migrationAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var idsServerdbPassword = Configuration["DbContextSettings:DbPassword"];
var idsServerbuilder = new NpgsqlConnectionStringBuilder(idsServerConnectionString)
{
Password = dbPassword
};
var idBuilder = 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/Login";
options.Authentication = new AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromMinutes(10),
CookieSlidingExpiration = true
};
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
options.EnableTokenCleanup = true;
}).AddAspNetIdentity<MembershipUser>()
.AddProfileService<ProfileService>();
X509Certificate2 cert = null;
using (var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
"thumbprint",
false);
if (certCollection.Count > 0)
{
cert = certCollection[0];
}
}
if (Environment.IsDevelopment())
{
idBuilder.AddDeveloperSigningCredential();
}
else
{
idBuilder.AddSigningCredential(cert);
}
idBuilder.Services.ConfigureExternalCookie(options =>
{
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode)(-1);
});
idBuilder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode)(-1);
});
services.AddMediatR(typeof(Startup));
RegisterServices(services);
}
private void RegisterServices(IServiceCollection services)
{
services.AddSingleton<IEventBus, RabbitMQBus>(sp =>
{
var scopeFactory = sp.GetRequiredService<IServiceScopeFactory>();
return new RabbitMQBus(sp.GetService<IMediator>(), scopeFactory);
});
services.AddTransient<UserDBContext>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// uncomment if you want to add MVC
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
Can I store the cookies into SessionStore using MemoryCacheTicketStore ? Kindly share your thoughts.
A potential thing could be that IIS thinks the cookie header is too long.
By default ASP.NET Core chunks up the cookie in 4Kb chunks, like this picture shows:
So either you try to reduce the size of the cookie or look at the IIS settings, if you can increase the max header length?
Alternatively, you stop saving the tokens inside the cookie, by setting:
options.SaveTokens = false;
Now you of course need top store it somewhere else, like in a tokenstore.
I use identity server 4 with blazor server side client
everything is ok but token not authorized api methods but token works in server authorized controllers
is something wrong with grant type or code flow ?
server config class :
public static class Configurations
{
public static IEnumerable<IdentityResource> GetIdentityResources() =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiResource> GetApis() =>
new List<ApiResource> {
new ApiResource("api1")
};
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
// backward compat
new ApiScope("api1")
};
}
public static IEnumerable<Client> GetClients() => new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = {
"api1" ,
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
RedirectUris = { "https://localhost:44372/signin-oidc" },
AlwaysIncludeUserClaimsInIdToken = true,
AllowOfflineAccess = true,
RequireConsent = false,
RequirePkce = true,
}
};
}
server start up class :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(config =>
{
config.UseInMemoryDatabase("Memory");
});
// AddIdentity registers the services
services.AddIdentity<IdentityUser, IdentityRole>(config =>
{
config.Password.RequiredLength = 4;
config.Password.RequireDigit = false;
config.Password.RequireNonAlphanumeric = false;
config.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "IdentityServer.Cookie";
config.LoginPath = "/Auth/Login";
config.LogoutPath = "/Auth/Logout";
});
services.AddIdentityServer()
.AddAspNetIdentity<IdentityUser>()
//.AddInMemoryApiResources(Configurations.GetApis())
.AddInMemoryIdentityResources(Configurations.GetIdentityResources())
.AddInMemoryApiScopes(Configurations.GetApiScopes())
.AddInMemoryClients(Configurations.GetClients())
.AddDeveloperSigningCredential();
services.AddControllersWithViews();
}
api start up class :
services.AddAuthentication("Bearer").AddIdentityServerAuthentication(option =>
{
option.Authority = "https://localhost:44313";
option.RequireHttpsMetadata = false;
option.ApiName = "api1";
});
blazor server side start up class:
services.AddAuthentication(config =>
{
config.DefaultScheme = "Cookie";
config.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookie")
.AddOpenIdConnect("oidc", config =>
{
config.Authority = "https://localhost:44313/";
config.ClientId = "client";
config.ClientSecret = "secret";
config.SaveTokens = true;
config.ResponseType = "code";
config.SignedOutCallbackPath = "/";
config.Scope.Add("openid");
config.Scope.Add("api1");
config.Scope.Add("offline_access");
});
services.AddMvcCore(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser() // site-wide auth
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
To fix this issue, you have 2 options:
1- (Recommended) To add the scopes to API resource like this:
public static IEnumerable<ApiResource> GetApis() =>
new List<ApiResource> {
new ApiResource("api1")
{
Scopes = new []{ "api1" }
}
};
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
// backward compat
new ApiScope("api1")
};
}
2- On API change your code to set ValidateAudience = false, like this:
services.AddAuthentication("Bearer").AddJwtBearer("Bearer",
options =>
{
options.Authority = "http://localhost:5000";
options.Audience = "api1";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new
TokenValidationParameters()
{
ValidateAudience = false
};
});
Here is my blog about migrating IdentityServer4 to v4 https://nahidfa.com/posts/migrating-identityserver4-to-v4/
I have not actually used AddIdentityServerAuthentication in API but can you try the below code. Technically its same thing, but maybe this will work.
Change your api authentication from AddIdentityServerAuthentication to AddJwtBearer:
services.AddAuthentication("Bearer").AddIdentityServerAuthentication(option =>
{
option.Authority = "https://localhost:44313";
option.RequireHttpsMetadata = false;
option.ApiName = "api1";
});
to
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", option =>
{
option.Authority = "https://localhost:44313";
option.Audience = "api1";
option.SaveToken = true;
});
Most recently, the software is shutting down periodically, and this is usually displayed in the logs:
Unable to bind to http://localhost:28575 on the IPv4 loopback interface: 'Error -4092 EACCES permission denied'.
This port(28575) is constantly changing! Although I've used this code:
.UseUrls("http://localhost:29983")
Of course, sometimes, without recording any exception in logs, an error 403 is displayed and the software shutting down.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddDbContext<AppDbContext>(options => options.UseSqlServer("Server =.;Database=***;User Id=***;Password=***;MultipleActiveResultSets=true;",b=> b.UseRowNumberForPaging()));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireDigit = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredUniqueChars = 1;
}).AddEntityFrameworkStores<AppDbContext>().AddDefaultTokenProviders();
services.AddSingleton<IUnitOfWork, UnitOfWork>();
// store cache in db :
services.AddSession();
services.AddSingleton<Microsoft.AspNetCore.Authentication.Cookies.ITicketStore, Controllers.DistributedCacheTicketStore>();
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = #"Server =.;Database=***;User Id=***;Password=***;MultipleActiveResultSets=true;";
options.SchemaName = "dbo";
options.TableName = "AppSqlCache";
});
services.AddAuthorization(options =>
{
options.AddPolicy("myAuthorize", policy => policy.Requirements.Add(new Controllers.HasScopeRequirement("myAuthorize")));
});
services.AddSingleton<Microsoft.AspNetCore.Authorization.IAuthorizationHandler, Controllers.HasScopeHandler>();
services.AddMvcActionsDiscoveryService();
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.SessionStore = services.BuildServiceProvider().GetService<Microsoft.AspNetCore.Authentication.Cookies.ITicketStore>();
options.ExpireTimeSpan = TimeSpan.FromDays(5);
options.SlidingExpiration = true;
options.AccessDeniedPath = "/Home/AccessDenied";
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
});
services.AddMvc();
services.Configure<GzipCompressionProviderOptions>(options => options.Level = System.IO.Compression.CompressionLevel.Optimal);
services.AddResponseCompression();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStatusCodePagesWithReExecute("/Home/error/{0}");
app.UseExceptionHandler("/Home/error/{0}");
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = context =>
{
// Cache static file for 1 year
if (!string.IsNullOrEmpty(context.Context.Request.Query["v"]))
{
context.Context.Response.Headers.Add("cache-control", new[] { "public,max-age=2628000" }); // month=2628000, year = 31536000
context.Context.Response.Headers.Add("Expires", new[] { DateTime.UtcNow.AddMonths(1).ToString("R") }); // Format RFC1123
}
}
});
app.UseResponseCompression();
app.UseAuthentication();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute("AreaRoute", "{area:exists}/{controller=Account}/{action=Login}/{id?}");
routes.MapRoute("FirstPage", "{controller=Account}/{action=Login}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
SeedData.EnsurePopulated(app);
}
I got this error today. In my case there was a OS update pending on my laptop. I'm not certain why that should be preventing access to a port but a re-start did the trick for me.
I am working with an Angular 2 app with asp.net core back end. I am trying to print a pdf (client side code below). When I run on our dev server, everything is ok; however, running on production I get
Failed to load api_url: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin url is therefore not allowed access.
Everything I have seen mentions something about CORS policy, but I don't understand how this could be working fine on one server, but not on another. Also, it appears to be retrieving fine when hitting other API endpoints.
Client-side api call:
getPDF(pickupId: string): void {
this.printingSub.next(true);
this._http.get(this._dataUrl + 'pickupsheet?pickupid=' + pickupId + '&barcode=true', { responseType: ResponseContentType.Blob })
.catch(error => this.handleError(error))
.subscribe((response: Response) => {
this.pdfBlob = new Blob([response.blob()], { type: 'application/pdf' });
const blobUrl = URL.createObjectURL(this.pdfBlob);
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = blobUrl;
document.body.appendChild(iframe);
iframe.contentWindow.print();
this.printingSub.next(false);
});
}
Startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
public IConfigurationSection AppSettings { get; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile(#"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
AppSettings = Configuration.GetSection("appSettings");
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
ConfigureDatabase();
ConfigurePolicies(services);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddOptions();
services.Configure<AppAccessSettings>(s =>
{
s.Env = AppSettings.GetSection("env").Value;
s.EnableAuth = bool.Parse(AppSettings.GetSection("enableAuth").Value);
});
services.AddMvc().AddJsonOptions(options =>
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver()
);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Configure JWT authentication
Authentication.SetVarFromFile(AppSettings.GetSection("authFile").Value);
Authentication.SetAuth(ref app, AppSettings.GetSection("audience").Value);
app.UseCors("CorsPolicy");
app.UseMvc();
}
private void ConfigureDatabase()
{
string dbSource = AppSettings.GetSection("env").Value;
OracleEnv.Connection = Configuration.GetSection("connectionStrings").GetSection(dbSource).Value;
}
private void ConfigurePolicies(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddAuthorization(options =>
{
options.AddPolicy("EnableAuth",
policy => policy.Requirements.Add(new AuthRequirement(Configuration)));
});
services.AddSingleton<IAuthorizationHandler, UserAuthHandler>();
}
}
private void ConfigurePolicies(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddAuthorization(options =>
{
options.AddPolicy("EnableAuth",
policy => policy.Requirements.Add(new AuthRequirement(Configuration)));
});
services.AddSingleton<IAuthorizationHandler, UserAuthHandler>();
}
Pickup Sheet API Method
[Route("PickupSheet")]
public IActionResult GetPickupSheet(string pickupId, bool barCode)
{
PbReportGenerator rpt = new PbReportGenerator();
byte[] report = rpt.RetrievePDFReport(232, new Dictionary<string, string>
{
{ pickupId, "string" },
{ (barCode ? 1 : 0).ToString(), "int" }
});
var stream = new MemoryStream(report);
var response = File(stream, "application/pdf", String.Format("Report232_{0}.pdf", pickupId));
return response;
}
You need to set withCredentials with each request:
this._http.get(URL, { responseType: ResponseContentType.Blob, withCredentials: true })
I am trying to use JwtBearerAuthentication in Asp core 2.0 and I've encountered two main issues.
The Configure method of startup is like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseTestSensitiveConfiguration(null);
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
and the ConfigureServices like bellow:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver())
.AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services.AddDbContext<FGWAContext>(options => options.UseSqlServer(connection));
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<FGWAContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 4;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// User settings
options.User.RequireUniqueEmail = true;
});
//// If you want to tweak Identity cookies, they're no longer part of IdentityOptions.
//services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
//services.AddAuthentication();
//// If you don't want the cookie to be automatically authenticated and assigned to HttpContext.User,
//// remove the CookieAuthenticationDefaults.AuthenticationScheme parameter passed to AddAuthentication.
//services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
// .AddCookie(options =>
// {
// options.LoginPath = "/Account/LogIn";
// options.LogoutPath = "/Account/LogOff";
// });
//services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
// .AddJwtBearer(jwtBearerOptions =>
// {
// //jwtBearerOptions.Events.OnChallenge = context =>
// //{
// // context.Response.Headers["Location"] = context.Request.Path.Value;
// // context.Response.StatusCode = 401;
// // return Task.CompletedTask;
// //};
// jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
// {
// ValidateIssuerSigningKey = true,
// IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your secret goes here")),
// ValidateIssuer = true,
// ValidIssuer = "The name of the issuer",
// ValidateAudience = true,
// ValidAudience = "The name of the audience",
// ValidateLifetime = true, //validate the expiration and not before values in the token
// ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
// };
// });
// Enable Dual Authentication
services.AddAuthentication()
.AddCookie(cfg => cfg.SlidingExpiration = true)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
});
services.AddTransient<IModelsService, ModelsService>();
services.AddTransient<IRestaurantService, RestaurantService>();
}
and the two main issues:
1- It does not work! I call the method to generate token http://localhost:59699/api/accountapi/login the the answer is something like this:
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb29kdGVzdGdldHVzckBnbWFpbC5jb20iLCJqdGkiOiIyZGQ0MDhkNy02NDE4LTQ2MGItYTUxYi1hNTYzN2Q0YWYyYzgiLCJpYXQiOiIxMC8xMi8yMDE3IDM6NDA6MDYgQU0iLCJuYmYiOjE1MDc3Nzk2MDYsImV4cCI6MTUwNzc3OTkwNiwiaXNzIjoiRXhhbXBsZUlzc3VlciIsImF1ZCI6IkV4YW1wbGVBdWRpZW5jZSJ9.of-kTEIG8bOoPfyCQjuP7s6Zm32yFFPlW_T61OT8Hqs","expires_in":300}
then I call the protected resource this way:
but the protected resource is not accessible.
2- After failure to authenticate, it redirects the request to login page. How can I disable this auto challenge behavior?
before you start answering I have to tell that, I have tried https://wildermuth.com/2017/08/19/Two-AuthorizationSchemes-in-ASP-NET-Core-2 to craete the authentication and this https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x too; and also this ASP.NET Core 2.0 disable automatic challenge to disable auto challenge, but non of them worked.
Update
The desired config is to use jwtbearerauthentication on webapi calls and for the others use cookie. Then while using the former I want to return not authorized (401) response on a not authorized request while for the later one I want a redirection.
I see at least one error. Your Authorization header should be "Bearer TOKEN", not "bearer". So capitalize Bearer.
Then for the redirection of the api call. Put the Authorize attribute with the JwtBearer schema on the api actions:
[Route("api/method")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
public IActionResult MyApiCall()
{
.....
}