Change Global Settings Config in SignalR Core - asp.net-mvc

I am using SignalR Core with ASP.Net Core.
I want to override GlobalHost settings for signalR.
I am getting this-
protected void Application_Start(object sender, EventArgs e)
{
// Make long polling connections wait a maximum of 110 seconds for a
// response. When that time expires, trigger a timeout command and
// make the client reconnect.
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
// Wait a maximum of 30 seconds after a transport connection is lost
// before raising the Disconnected event to terminate the SignalR connection.
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);
// For transports other than long polling, send a keepalive packet every
// 10 seconds.
// This value must be no more than 1/3 of the DisconnectTimeout value.
GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);
RouteTable.Routes.MapHubs();
}
But I can't configure it with my application.
It is on ASP.Net Core v1.0.
My Startup.cs is like this-
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace McpSmyrilLine
{
public class Startup
{
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)
.AddEnvironmentVariables();
if (env.IsDevelopment())
{
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
}
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
//Add DB Context
var connectionStringBuilder = new Microsoft.Data.Sqlite.SqliteConnectionStringBuilder { DataSource = "mcp.db" };
var connectionString = connectionStringBuilder.ToString();
///////////////Add Cors
var corsBuilder = new CorsPolicyBuilder();
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.AllowAnyOrigin();
corsBuilder.AllowCredentials();
services.AddCors(options =>
{
options.AddPolicy("AllowAll", corsBuilder.Build());
});
///////////////End Cors
services.AddDbContext<McpDbContext>(options =>
options.UseSqlite(connectionString));
services.AddMvc();
services.AddSignalR(options => { options.Hubs.EnableDetailedErrors = true; });
}
// 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 Cors
app.UseCors("AllowAll");
app.UseSignalR();
app.UseApplicationInsightsRequestTelemetry();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
//Adding Seeder Data
AddTestData(app.ApplicationServices.GetService<McpDbContext>());
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseApplicationInsightsExceptionTelemetry();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
private static void AddTestData(McpDbContext context)
{
//var testUser1 = new DbModels.Try.User
//{
// Id = "abc123",
// FirstName = "Luke",
// LastName = "Skywalker"
//};
//context.Users.Add(testUser1);
//var testPost1 = new DbModels.Try.Post
//{
// Id = "def234",
// UserId = testUser1.Id,
// Content = "What a piece of junk!"
//};
//context.Posts.Add(testPost1);
//context.SaveChanges();
}
}
}
Can anyone please help?

There's a Transports property in SignalROptions
You can setup the SignalR middleware like this :
services.AddSignalR(options => {
options.Hubs.EnableDetailedErrors = true;
var transports = options.Transports;
transports.DisconnectTimeout = TimeSpan.FromSeconds(30);
transports.KeepAlive = TimeSpan.FromSeconds(10);
transports.TransportConnectTimeout = TimeSpan.FromSeconds(110);
});
UPDATE alpha2-final
Transport options can be configured throught MapHub:
app.UseSignalR(configure =>
{
configure.MapHub<Hub>("hub", options =>
{
options.Transports = TransportType.All;
options.LongPolling.PollTimeout = TimeSpan.FromSeconds(10);
options.WebSockets.CloseTimeout = TimeSpan.FromSeconds(10);
});
})
and on client side:
let logger: ILogger;
let transportType: TransportType;
const hubConnetion = new HubConnection(
new HttpConnection(
url,
{
transport: transportType,
logging: logger
}));

Related

Issues getting an api with swagger to authenticate with IdentityServer4 using .net5.0

I am currently learning how microservices work for an application i am building for my portfolio and for a small community of people that want this specific application. I have followed a tutorial online and successfully got IdentityServer4 to authenticate an MVC Client, however, I am trying to get swagger to work alongside the API's especially if they require authentication. Each time I try to authorize swagger with IdentityServer4, I am getting invalid_scope error each time I try authenticate. I have been debugging this problem for many hours an am unable to figure out the issue. I have also used the Microsoft eShopOnContainers as an example but still no luck. Any help would be greatly appreciated. Ill try keep the code examples short, please request any code not shown and ill do my best to respond asap. Thank you.
Identiy.API project startup.cs:
public class Startup {
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddInMemoryClients(Config.GetClients())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryApiScopes(Config.GetApiScopes())
.AddTestUsers(Config.GetTestUsers())
.AddDeveloperSigningCredential(); // #note - demo purposes only. need X509Certificate2 for production)
services.AddControllersWithViews();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseStaticFiles();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
Config.cs (i removed the test users to keep the code shorter, since it is not relevant).
#note -I created a WeatherSwaggerUI client specifically for use with swagger since this was apart of eShopOnContainers example project provided by microsoft:
public static class Config
{
public static List<TestUser> GetTestUsers()
{
return new List<TestUser>(); // removed test users for this post
}
public static IEnumerable<Client> GetClients()
{
// #note - clients can be defined in appsettings.json
return new List<Client>
{
// m2m client credentials flow client
new Client
{
ClientId = "m2m.client",
ClientName = "Client Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("SuperSecretPassword".ToSha256())},
AllowedScopes = { "weatherapi.read", "weatherapi.write" }
},
// interactive client
new Client
{
ClientId = "interactive",
ClientSecrets = {new Secret("SuperSecretPassword".Sha256())},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {"https://localhost:5444/signin-oidc"},
FrontChannelLogoutUri = "https://localhost:5444/signout-oidc",
PostLogoutRedirectUris = {"https://localhost:5444/signout-callback-oidc"},
AllowOfflineAccess = true,
AllowedScopes = {"openid", "profile", "weatherapi.read"},
RequirePkce = true,
RequireConsent = false,
AllowPlainTextPkce = false
},
new Client
{
ClientId = "weatherswaggerui",
ClientName = "Weather Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = {"https://localhost:5445/swagger/oauth2-redirect.html"},
PostLogoutRedirectUris = { "https://localhost:5445/swagger/" },
AllowedScopes = { "weatherswaggerui.read", "weatherswaggerui.write" },
}
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("weatherapi", "Weather Service")
{
Scopes = new List<string> { "weatherapi.read", "weatherapi.write" },
ApiSecrets = new List<Secret> { new Secret("ScopeSecret".Sha256()) },
UserClaims = new List<string> { "role" }
},
new ApiResource("weatherswaggerui", "Weather Swagger UI")
{
Scopes = new List<string> { "weatherswaggerui.read", "weatherswaggerui.write" }
}
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> { "role" }
},
};
}
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
// weather API specific scopes
new ApiScope("weatherapi.read"),
new ApiScope("weatherapi.write"),
// SWAGGER TEST weather API specific scopes
new ApiScope("weatherswaggerui.read"),
new ApiScope("weatherswaggerui.write")
};
}
}
Next project is the just the standard weather api when creating a web api project with vs2019
WeatherAPI Project startup.cs (note i created extension methods as found in eShopOnContainers as i liked that flow):
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.AddControllers();
services.AddCustomAuthentication(Configuration)
.AddSwagger(Configuration);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger()
.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Weather.API V1");
options.OAuthClientId("weatherswaggerui");
options.OAuthAppName("Weather Swagger UI");
});
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
public static class CustomExtensionMethods
{
public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
{
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Find Scrims - Weather HTTP API Test",
Version = "v1",
Description = "Randomly generates weather data for API testing"
});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
Implicit = new OpenApiOAuthFlow()
{
AuthorizationUrl = new Uri($"{ configuration.GetValue<string>("IdentityUrl")}/connect/authorize"),
TokenUrl = new Uri($"{ configuration.GetValue<string>("IdentityUrl")}/connect/token"),
Scopes = new Dictionary<string, string>()
{
{ "weatherswaggerui", "Weather Swagger UI" }
},
}
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
return services;
}
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
var identityUrl = configuration.GetValue<string>("IdentityUrl");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "weatherapi";
});
return services;
}
}
Lastly is the AuthorizeCheckOperationFilter.cs
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
if (!hasAuthorize) return;
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
var oAuthScheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "oauth2"
}
};
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[oAuthScheme ] = new [] { "weatherswaggerui" }
}
};
}
}
again, any help, or recommendations on a guide would be greatly appreciated as google has not provided me with any results to fixing this issue. Im quite new to IdentityServer4 and am assuming its a small issue due with clients and ApiResources and ApiScopes. Thank you.
The swagger client needs to access the api and to do so it requires api scopes. What you have for swagger scopes are not doing this. Change the scopes for swagger client ‘weatherswaggerui’ to include the api scopes like this:
AllowedScopes = {"weatherapi.read"}

Exception occurred while processing message. System.InvalidOperationException: IDX20803: Unable to obtain configuration from: '[PII is hidden

I am following this tutorial which secures my Blazor WebAssembly Apps with Auth2.com and I am using command line to run the program. I could completed all the steps successfully and I could the Blazor app. But for the last step, I got this error from the command line
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
Exception occurred while processing message. System.InvalidOperationException: IDX20803: Unable to obtain configuration from: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. ---> System.ArgumentException: IDX20108: The address specified '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' is not valid as per HTTPS scheme. Please specify an https address for security reasons. If you want to test with http address, set the RequireHttps property on IDocumentRetriever to false. (Parameter 'address')
and in my browser console, I get error below:
This is QuizViewer.razor which consume the API
#page "/quizViewer"
#attribute [Authorize]
#using QuizManagerClientHosted.Shared
#using System.Net.Http.Json
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
#using System.Net.Http.Headers
#inject HttpClient Http
#inject IAccessTokenProvider TokenProvider
<h1>Take your quiz!</h1>
<p>Your current score is #currentScore</p>
#if (quiz == null)
{
<p><em>Loading...</em></p>
}
else
{
int quizIndex = 0;
#foreach (var quizItem in quiz)
{
<section>
<h3>#quizItem.Question</h3>
<div class="form-check">
#{
int choiceIndex = 0;
quizScores.Add(0);
}
#foreach (var choice in quizItem.Choices)
{
int currentQuizIndex = quizIndex;
<input class="form-check-input" type="radio" name="#quizIndex" value="#choiceIndex" #onchange="#((eventArgs) => UpdateScore(Convert.ToInt32(eventArgs.Value), currentQuizIndex))" />#choice<br>
choiceIndex++;
}
</div>
</section>
quizIndex++;
}
}
#code {
List<QuizItem> quiz;
List<int> quizScores = new List<int>();
int currentScore = 0;
protected override async Task OnInitializedAsync()
{
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "quiz"))
{
var tokenResult = await TokenProvider.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token.Value);
var response = await Http.SendAsync(requestMessage);
quiz = await response.Content.ReadFromJsonAsync<List<QuizItem>>();
}
}
}
void UpdateScore(int chosenAnswerIndex, int quizIndex)
{
var quizItem = quiz[quizIndex];
if (chosenAnswerIndex == quizItem.AnswerIndex)
{
quizScores[quizIndex] = quizItem.Score;
}
else
{
quizScores[quizIndex] = 0;
}
currentScore = quizScores.Sum();
}
}
and this is the API controller
using QuizManagerClientHosted.Shared;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace QuizManagerClientHosted.Server.Controllers
{
[ApiController]
[Route("[controller]")]
[Authorize]
public class QuizController : ControllerBase
{
private static readonly List<QuizItem> Quiz = new List<QuizItem> {
new QuizItem
{
Question = "Which of the following is the name of a Leonardo da Vinci's masterpiece?",
Choices = new List<string> {"Sunflowers", "Mona Lisa", "The Kiss"},
AnswerIndex = 1,
Score = 3
},
new QuizItem
{
Question = "Which of the following novels was written by Miguel de Cervantes?",
Choices = new List<string> {"The Ingenious Gentleman Don Quixote of La Mancia", "The Life of Gargantua and of Pantagruel", "One Hundred Years of Solitude"},
AnswerIndex = 0,
Score = 5
}
};
[HttpGet]
public List<QuizItem> Get()
{
return Quiz;
}
}
}
and Server startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace QuizManagerClientHosted.Server
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = Configuration["Auth0:Authority"];
options.Audience = Configuration["Auth0:ApiIdentifier"];
});
services.AddControllersWithViews();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
}
}
How do I fix this? Which part I configure wrongly?
Each account in Auth0 has a discovery endpoint and you can find it by going to Settings for your Auth0 application and then under Advanced settings you will find the Endpoints tab.
Under there you will find your OpenID Configuration URL.
If you're following the Blazor WASM tutorial and you run into this error what most likely happened is you added https:// to the Domain in Server/appsettings.json
{ "Auth0": { "Domain": "https://something.auth0.com" } }
The example code for the Server/Program.cs uses string interpolation to add the https://
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, c =>
{
// HERE
c.Authority = $"https://{builder.Configuration["Auth0:Domain"]}";
c.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = builder.Configuration["Auth0:Audience"],
// AND HERE
ValidIssuer = $"https://{builder.Configuration["Auth0:Domain"]}"
};
});
So either remove https:// from the Server/appsettings.json or remove it from Server/Program.cs

Cannot get user identity set in Web Core API v1

I have a Web Core API version 1 project, where when I call a method via Postman I find the [Authorize] tag does not work.
In my Web API my startup looks like this (edited for readability)
public void ConfigureServices(IServiceCollection services)
{
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(typeof(Startup).Assembly));
services.AddSingleton(manager);
services.AddCors();
services.AddMvcCore().AddJsonFormatters();
services.Configure<IISOptions>(options => new IISOptions
{
AutomaticAuthentication = true,
ForwardClientCertificate = false,
ForwardWindowsAuthentication = false
});
}
// 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)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler();
}
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
RequireHttpsMetadata = false,
Authority = Settings.AuthorityUrl,
ApiName = Settings.ApiName
});
app.UseStaticFiles();
var url = Configuration["originUrl"];
app.UseCors(
options => options.WithOrigins(url).AllowAnyHeader().AllowAnyMethod().AllowCredentials()
);
app.UseMiddleware<StackifyMiddleware.RequestTracerMiddleware>();
app.UseMvc();
}
In my OAuth server I am using the Quickstart for IdentityServer4
public class Startup
{
// 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.AddIdentityServer()
.AddSigningCredential(new X509Certificate2(Settings.CertPath, Settings.Password))
.AddTestUsers(InMemoryConfiguration.Users().ToList())
.AddInMemoryClients(InMemoryConfiguration.Clients())
.AddInMemoryApiResources(InMemoryConfiguration.ApiResources());
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.RequireHttpsMetadata = false;
options.Authority = Settings.AuthorityUrl;
options.ApiName = Settings.ApiName;
});
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, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMiddleware<StackifyMiddleware.RequestTracerMiddleware>();
app.UseIdentityServer();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
And this is the method I am calling in Postman;
[HttpGet("get")]
public async Task<IActionResult> Get()
{
var claims = User.Claims;
var username = User.Identity.Name;
this.NLogger.Info("api/comboboxdata/get".ToPrefix());
try
{
var container = new ComboBoxData(this.SirUoW);
return Ok(container);
}
catch (Exception e)
{
var message = "Error getting combo box data";
await ReportException(e, message);
var status = OperationStatus.CreateFromException(message, e);
return BadRequest(status);
}
}
In Postman I get the bearer token and put it in the header. The method is successfully called and returns the data. The claims are also set as expected, and when the token expires the claims are empty. However [Authorize] does not block the request if the token is invalid or is not sent.
The Authorize attribute is at the beginning of the controller;
[Authorize]
[Route("api/comboboxdata")]
public class ComboBoxDataController : BaseSirController
{
How do I put this right?
You should add AddAuthorizationmethod to enable authorization services in your web api :
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();

OAuth2 Callback not hitting handler

When the server I'm authenticating with responds with the redirect back to my callback URL, my code doesn't seem to know how to handle the request - as if I'm missing a handler.
I thought the CallbackPath property would wire this up and pass the incoming request to my handler, is that not correct?
I just get a 404 from my app, the URL has the code=XXXX etc.
Here's my code
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)
{
string authScheme = CookieAuthenticationDefaults.AuthenticationScheme;
services.AddAuthentication(o =>
{
o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultAuthenticateScheme = "MyAuthScheme";
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddOAuth(authScheme, options =>
{
options.ClientId = "xxxxxxxxxxxxxxxxxxxxxxxx";
options.AuthorizationEndpoint = "https://example.com/adfs/oauth2/authorize?resource=https://example.net/";
options.TokenEndpoint = "https://example.com/adfs/oauth2/token";
options.CallbackPath = new Microsoft.AspNetCore.Http.PathString("/signin-myauthserver");
options.ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxx";
});
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
}
// 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.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
}

Asp.Net MVC 6 Identity 3 MongoDB External Login (Facebook)

I'm running the RC1 of ASP.NET MVC 6 and would like to use a MongoDB Identity Provider.
I have implemented the provider by Grant Megrabyan which is doing a great job of registering new users and allowing them to log in but I get the error:
InvalidOperationException: No authentication handler is configured to handle the scheme: Microsoft.AspNet.Identity.External
Microsoft.AspNet.Http.Authentication.Internal.DefaultAuthenticationManager.d__13.MoveNext()
I had the external login previously working using EntityFramework so I'm assuming my configuration for third party auth is probably correct.
When the user clicks login with Facebook they are redirected to the following action :
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
There is no exception thrown at this point however when the Facebook returns from the ChallengeResponse it sends a GET to : http://localhost:51265/signin-facebook?code=[facebook user token].
At this point ASP.NET throws the exception:
The callback url made by Facebook doesn't seem to make sense. Surely it should return to my ExternalLoginCallback action?
It's about here that I'm out of ideas?!
If anyone can see where I've gone wrong then I'd be a very happy guy.
My startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ApplicationDbContext>();
// Add framework services.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddMongoStores<ApplicationDbContext, ApplicationUser, IdentityRole>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// 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();
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseStaticFiles();
app.UseFacebookAuthentication(options =>
{
options.AppId = "removed";
options.AppSecret = "removed";
});
app.UseIdentity();
app.UseMvcWithDefaultRoute();
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
Call UseFacebookAuthentication after UseIdentity, but before UseMvc;
app.UseIdentity();
app.UseFacebookAuthentication(options =>
{
options.AppId = "removed";
options.AppSecret = "removed";
});
app.UseMvcWithDefaultRoute();
Edit:
I think the problem is the Order of defining configurations. Try to add Identity first then Facebook Authentication.
** Suggested Example **
app.UseIdentity();
app.UseMvcWithDefaultRoute();
app.UseFacebookAuthentication(options =>
{
options.AppId = "removed";
options.AppSecret = "removed";
});

Resources