I use my own JWT token authentication and not the asp.net identity that comes free with the default template. I've looked everywhere for some documentation/guidence on how to implement exernal authentication without asp.net identity but all articles out there is for the asp.net identity authentication.
I've managed to redirect the user to the google log in page, (using ChallengeResult), but when the provider redirects back the app fails me.
I've removed: app.UseAuthentication(); in Startup.cs, (disabling authentication), and I was then able to reach the callback function but then I had no idea how to retrieve the data from the respons without using the sign in manager..
Startup
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Authentication:Secret"]));
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = Configuration["Urls:Base"],
ValidateAudience = true,
ValidAudience = Configuration["Urls:Base"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o =>
{
o.TokenValidationParameters = tokenValidationParameters;
}
).AddGoogle(googleOptions =>
{
googleOptions.ClientId = "x";//Configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = "x";//Configuration["Authentication:Google:ClientSecret"];
googleOptions.CallbackPath = "/api/authentication/externalauthentication/externallogincallback";
});
services.Configure<RequestLocalizationOptions>(
opts =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en"),
new CultureInfo("sv")
};
opts.DefaultRequestCulture = new RequestCulture(culture: "en", uiCulture: "en");
opts.SupportedCultures = supportedCultures;
opts.SupportedUICultures = supportedCultures;
});
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.RegisterAppSettings(Configuration);
services.AddOptions();
services.InjectServices();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
EndpointsAppSettings endpointAppSettings = new EndpointsAppSettings();
Configuration.GetSection("Endpoints").Bind(endpointAppSettings);
app.UseCors(builder =>
{
builder.WithOrigins(endpointAppSettings.Aurelia)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
}
var logService = app.ApplicationServices.GetService<ILogService>();
loggerFactory.AddProvider(new LogProvider(logService));
app.UseRequestLocalization(app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>().Value);
app.UseMvc();
app.UseDefaultFiles();
app.UseStaticFiles();
}
}
Controller
[Route("api/authentication/[controller]")]
public class ExternalAuthenticationController : Controller
{
[AllowAnonymous]
[HttpPost(nameof(ExternalLogin))]
public IActionResult ExternalLogin(ExternalLoginModel model)
{
if (model == null || !ModelState.IsValid)
{
return null;
}
var properties = new AuthenticationProperties { RedirectUri = "http://localhost:3000/#/administration/organisations" };
return Challenge(properties, model.Provider);
}
[AllowAnonymous]
[HttpGet(nameof(ExternalLoginCallback))]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
return null;
}
//Help me retrieve information here!
return null;
}
}
Stack trace for ExternalLoginCallback
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] Request starting HTTP/1.1 GET http://localhost:5000/api/authentication/externalauthentication/externallogincallback?state=CfDJ8CyKJfDTf--HIDDEN DATA--52462e4156a..5cde&prompt=none fail: Microsoft.AspNetCore.Server.Kestrel[13] Connection id "0HLAKEGSHERH7", Request id "0HLAKEGSHERH7:00000002": An unhandled exception was thrown by the application. System.InvalidOperationException: No IAuthenticationSignInHandler is configured to handle sign in for the scheme: Bearer at Microsoft.AspNetCore.Authentication.AuthenticationService.d__13.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler d__12.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.d__6.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.d__3.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Frame`1.d__2.MoveNext()
The CookieAuthenticationDefaults. AuthenticationScheme GitHub Source shows it's set to "Cookies" . The authentication cookie's IsEssential property is set to true by default. Authentication cookies are allowed when a site visitor hasn't consented to data collection.
SignInAsync(HttpContext, String, ClaimsPrincipal, AuthenticationProperties) Sign in a principal for the specified scheme.
To solve the:
No IAuthenticationSignInHandler is configured to handle sign in for the scheme: Bearer
I had to add a cookie handler that will temporarily store the outcome of the external authentication, e.g. the claims that got sent by the external provider. This is necessary since there are typically a couple of redirects involved until you are done with the external authentication process.
Startup
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o =>
{
o.TokenValidationParameters = tokenValidationParameters;
})
.AddCookie()
.AddGoogle(googleOptions =>
{
googleOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
googleOptions.ClientId = "x";//Configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = "x";//Configuration["Authentication:Google:ClientSecret"];
//googleOptions.CallbackPath = "/api/authentication/externalauthentication/signin-google";
});
The important part here is CookieAuthenticationDefaults.AuthenticationScheme. This is a string constant that stores "Cookies". While we can directly use the string "Cookies" within our code, using the preset constant would be safer. This is the authentication scheme name given to the AddCookies
function by default. It helps you reference the cookies authentication.
Now it's time to retrieve the user information from the claims provided by the external authentication in the callback action.
Controller
[AllowAnonymous]
[HttpPost(nameof(ExternalLogin))]
public IActionResult ExternalLogin(ExternalLoginModel model)
{
if (model == null || !ModelState.IsValid)
{
return null;
}
var properties = new AuthenticationProperties { RedirectUri = _authenticationAppSettings.External.RedirectUri };
return Challenge(properties, model.Provider);
}
[AllowAnonymous]
[HttpGet(nameof(ExternalLoginCallback))]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
//Here we can retrieve the claims
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return null;
}
Voilà! We now have some user information to work with!
Helpful link
http://docs.identityserver.io/en/latest/topics/signin_external_providers.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With