Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Core External Authentication without ASP.NET Identity

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()

  1. Why am I getting: No IAuthenticationSignInHandler is configured to handle sign in for the scheme: Bearer and how is this solved?
  2. How do I retrieve user information in the ExternalLoginCallback action? With the default mvc template it was as easy as doing: var info = await _signInManager.GetExternalLoginInfoAsync(); but I'm not using the sign in manager.
  3. I haven't found any documentation at all about this, surely I can't be the only person that wants external authentication without using the built in jumbo dumbo asp.net identity? Please point me in the right direction if you're a better googler than me!
like image 462
Reft Avatar asked Jan 05 '18 20:01

Reft


People also ask

What is CookieAuthenticationDefaults AuthenticationScheme?

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.

What does HttpContext SignInAsync do?

SignInAsync(HttpContext, String, ClaimsPrincipal, AuthenticationProperties) Sign in a principal for the specified scheme.


1 Answers

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!

enter image description here

Helpful link

http://docs.identityserver.io/en/latest/topics/signin_external_providers.html

like image 96
Reft Avatar answered Oct 19 '22 23:10

Reft