Laden...

ASP.NET Core 3.1 Cookie Authentication leitet mich wieder zum Login zurück

Erstellt von Duesmannr vor 4 Jahren Letzter Beitrag vor 4 Jahren 2.025 Views
D
Duesmannr Themenstarter:in
161 Beiträge seit 2017
vor 4 Jahren
ASP.NET Core 3.1 Cookie Authentication leitet mich wieder zum Login zurück

Hey, ich schon wieder^^

Das Problem ist, wenn ich mich erfolgreich authentifiziere und ich mich mit SignInAsync anmelde, werde ich zum HomeController weitergeleitet.
Das Authorize leitet mich direkt wieder zur Login Page zurück, weil weiß ich nicht.

Die ValidateAsync Methode habe ich überprüft, das Cookie wird nicht rejected oder ich werde ausgeloggt.

Ich habe es auch mit einem Custom Authorize Attribute getestet um den Context zu überprüfen.
In dem Context bin ich angemeldet und authenticated.

Die Authentifizierung habe ich genau so in einem Asp.Net Core 2.1 Projekt und da läuft es einwandfrei..

Aber warum Authorize mich nicht durchlässt, ich weiß es nicht.
Habt Ihr Ideen?

Hier ist die Konfiguration:

HomeController.cs


public class HomeController : Controller
{
    [Authorize]
    public IActionResult Index()
    {
        return this.View("Index");
    }
}

Startup.cs


public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(options =>
            {
                options.LoginPath = "/auth/login/";
                options.ExpireTimeSpan = TimeSpan.FromDays(7);
                options.Events.OnValidatePrincipal = ValidateAsync;
            });

        services.AddControllersWithViews();

        services.AddAntiforgery();

        services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
        {
            options.UseSqlServer(this.Configuration.GetConnectionString("DefaultConnection"));
            options.EnableSensitiveDataLogging();
        });
    }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if(env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();
        app.UseAuthentication();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }

public static async Task ValidateAsync(CookieValidatePrincipalContext context)
    {
        context = context ?? throw new ArgumentNullException(nameof(context));

        String userId = context.Principal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value;

        if(userId == null)
        {
            context.RejectPrincipal();
            await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return;
        }

        ApplicationDbContext dbContext = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
        User user = await dbContext.Users.FindAsync(Guid.Parse(userId));

        if(user == null)
        {
            context.RejectPrincipal();
            await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return;
        }

        if(!user.StaySignedIn && 
            user.LastLogin != null && 
            (user.LastLogin.Subtract(TimeSpan.FromDays(1)) > DateTimeOffset.Now))
        {
            context.RejectPrincipal();
            await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return;
        }
    }

AuthController.cs


[Route("/login")]
    [Route("/auth/login")]
    public async Task<IActionResult> Login([FromForm]LoginModel loginModel)
    {
        Claim nameIdentifier = new Claim(ClaimTypes.NameIdentifier, user.Id.ToString());

        ClaimsIdentity userIdentity = new ClaimsIdentity(new List<Claim> { nameIdentifier }, CookieAuthenticationDefaults.AuthenticationScheme);
        ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(userIdentity);

        await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
        return this.RedirectToAction("Index", "Home");
    }

1.029 Beiträge seit 2010
vor 4 Jahren

Hi,

im Grunde kann ich nicht wirklich weiterhelfen - du versuchst hier quasi eine manuelle Cookie-Authentifizierung oder?

Fakt ist: Was du dort tust ist kein für ASP.NET Core nachvollziehbarer Login, womit du eine Art Authentication-Loop für den User gebaut hast.

Beispiel:
User ruft Home/Index auf
-> ASP.NET Core stellt fest: User ist nicht angemeldet
-> ASP.NET Core verweist Auth/Login
User postet seine Credentials
-> Du denkst, dass du den Benutzer erfolgreich anmeldest
-> Du leitest wieder auf Home/Index um
-> siehe Schritt 1 (ASP.NET Core stellt fest: User ist nicht angemeldet) und der Loop beginnt von vorn

Falls du das mit manuellen Cookies machen möchtest würde ich dir folgende Seite empfehlen:
https://docs.microsoft.com/de-de/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1

LG

D
Duesmannr Themenstarter:in
161 Beiträge seit 2017
vor 4 Jahren

im Grunde kann ich nicht wirklich weiterhelfen - du versuchst hier quasi eine manuelle Cookie-Authentifizierung oder?

Ja versuche ich.

Fakt ist: Was du dort tust ist kein für ASP.NET Core nachvollziehbarer Login, womit du eine Art Authentication-Loop für den User gebaut hast.

In .Net Core 2.1 funktioniert es wunderbar. Nur in 3.1 nicht mehr.

-> Du denkst, dass du den Benutzer erfolgreich anmeldest

Ich denke? Was macht dann HttpContext.SignIn() ? Den User anmelden eig.

-> siehe Schritt 1 (ASP.NET Core stellt fest: User ist nicht angemeldet) und der Loop beginnt von vorn

Ich habe noch keine konkrete Information dazu gefunden, wie das Attribut den Zugriff authoriziert.

Falls du das mit manuellen Cookies machen möchtest würde ich dir folgende Seite empfehlen:

>

Habe die Seite dabei die ganze Zeit offen 😃

16.806 Beiträge seit 2008
vor 4 Jahren

Ja versuche ich.

Und wieso machst das, wenn ASP.NET bereits einen sicheren Automatismus hat?

Genau wegen solch potentiell fehlerbehafteten Implementierungen hat ASP.NET Core gewisse Implementierungen, die man nicht umgehen kann, darunter zB das Authorize Attribut.
Zu viele Lücken und Probleme gab es in der Vergangenheit mit Flickschusterei.

Ich habe noch keine konkrete Information dazu gefunden, wie das Attribut den Zugriff authoriziert

In der Dokumentation steht's.

Ansonsten hab ich solch ein Validation Code schon mehrfach hier im Forum gepostet und auch in Warum IP-Adressen kein eindeutiges Merkmal sind dokumentiert.

Ich denke? Was macht dann HttpContext.SignIn() ? Den User anmelden eig.

"User Anmelden" gibt es im Web nicht.
SignIn setzt ein Cookie, mit dem ASP.NET dann von einem Identitätsmanagement spricht.
Ist der Cookie nicht da (was Du selbst kontrollieren kannst, wir nicht) oder fehlerhaft (was Du debuggen kannst, wir nicht) dann kann keine Identität erstellt werden.

Ein relativ grober Fehler ist jedoch Deine Middlewares: UseAuthentication muss vor UseAuthorization erfolgen.
Man kann nicht authorisieren wenn man nicht authentifziert hat.

Im Debug logging (beachte dazu das LogLevel) steht der Grund, wieso AuthN oder AuthZ fehltschlägt.

D
Duesmannr Themenstarter:in
161 Beiträge seit 2017
vor 4 Jahren

Und wieso machst das, wenn ASP.NET bereits einen sicheren Automatismus hat?

Der ist sicher. Wo soll der unsichere Faktor sein? Das dass authorize Attribute mich derzeit immer wieder zur Login Page weiterleitet hat ja was zu bedeuten und ich will wissen was.

In der Dokumentation steht's.

Und wo genau?

Ansonsten hab ich solch ein Validation Code schon mehrfach hier im Forum gepostet und auch in
>
dokumentiert.

Ich mache derzeit ja gar nichts mit IP-Adressen.

"User Anmelden" gibt es im Web nicht.

Das war grob formuliert.

Ein relativ grober Fehler ist jedoch Deine Middlewares: UseAuthentication muss vor UseAuthorization erfolgen.
Man kann nicht authorisieren wenn man nicht authentifziert hat.

Da stimme ich dir zu. Aber das es so wichtig ist, in welcher Reihenfolge man die Middleware used finde ich seltsam.

Edit: Ich hab es nun umgedreht und es funktioniert.
Das Authorize Attribute lässt mich nun durch. Daher verstehe ich dann immer noch nicht ganz genau, wo die Authentication unsicher sein soll.

Im Debug logging (beachte dazu das LogLevel) steht der Grund, wieso AuthN oder AuthZ fehltschlägt.

Dann debugge ich das nochmal genauer.

16.806 Beiträge seit 2008
vor 4 Jahren

Ich mache derzeit ja gar nichts mit IP-Adressen.

Hast kein Bock gehabt den Artikel zu lesen, wa? 😉
Dann hättest gemerkt, dass es gar nicht um die IP Adressen selbst geht, sondern wie man eine Identität in ASP.NET korrekt validiert (eben nicht über die IP).
Dann hättest eventuell auch nen Code Ausschnitt (und die korrekte Implementierung) gesehen.

Denn was Du hier in OnValidatePrincipal machst ist genau das: Identitätsvalidierung.

Aber das es so wichtig ist, in welcher Reihenfolge man die Middleware used finde ich seltsam.

Dann hast Du bislang nicht verstanden, wie das gesamte ASP.NET Core Pipeline Prinzip funktioniert.
Die Reihenfolge der Middleware ist der wichtigste Punkt des gesamten ASP.NET Frameworks.

Dein Code kann auch so nie unter 2.1 funktioniert haben, denn das Prinzip der Pipelines gibt es seit OWIN - und das ist so ca. 8 Jahre her.
Es war in 1.0, 1.1, 1.2, 2.0, 2.1 und auch in 3.0 und 3.1 komplett von der funktionalenweise her identisch.

Und mein persönliches empfinden ist, dass ich immer etwas Bauchschmerzen habe, wenn jemand eine eigene Identitätsverwaltung in ASP.NET schreibt - und dann die Grundprinzipien von ASP.NET noch nicht ganz verstanden hat.
Dahingehend eben auch meine Hinweise - auch aus Erfahrung.

Das Authorize Attribute lässt mich nun durch. Daher verstehe ich dann immer noch nicht ganz genau, wo die Authentication unsicher sein soll.

Wie Du sicherlich mitbekommen hast, gab es in den letzten Jahren immer mehr Datenleaks aus dem Web; im Bereich ASP.NET meistens, weil die Leute einfach scheisse ⚠ programmiert haben - nicht weil das Framework Bugs hatte.
Scheisse programmiert meist in dem Sinne, weil sie einfach kein Plan hatten, was sie machen - und sich meist auch überhaupt nicht damit beschäftigt sondern einfach drauf losgelegt haben.
Was soll dann auch dabei raus kommen...

Eines der größten Lücken war eine fehlerhafte Implementierung einer eigenen AuthorizeAttribute-Ableitung.
Daher hat das Team rund um Barry Dorrans beim Grunddesign von ASP.NET Core bereits sehr darauf geachtet, dass Security by Design verfolgt wird.

Und dazu gehört, dass gewisse Dinge in Sachen Security durch beschissenen Code einfach nicht mehr passieren kann, weshalb es zB. auch nicht mehr (so einfach) möglich ist ein eigenes Authorize Attribut zu haben (das dann eben scheisse ist).
Daher gibt es in ASP.NET Core auch die Policies, die den gleichen Zweck wie früher Custom Authorize Attribute erfüllen - nur eben unter dem Aspekt "Security by Design".

Auch andere Frameworks anderer Sprachen haben ähnliche Mechenismen angekündigt oder bereits implementiert - unter dem identischen Aspekt.

Und genau für diesen Gesamtzweck - Security by Design - gibt es ASP.NET Core Identity, das der allgemein empfohlene Weg für eine saubere Security by Design Lösung ist.
Es bringt eigene Provider mit, die man aber auch selbst im Rahmen der "Richtlinien" für eigene Bedürfnisse implementieren kann.

Entwickler wird man niemals dazu erziehen können, dass sie einfach mal 15 Minuten in die Dokumentation schauen. Wenn man aber Frameworks so gestaltet, dass das Design keine groben Lücken zulässt, ist vielen schon geholfen.

Falls Du Dich weiter interessierst, dann schau die Vorträge von Barry Dorrans aka blowdart auf YouTube an.

D
Duesmannr Themenstarter:in
161 Beiträge seit 2017
vor 4 Jahren

Dein Code kann auch so nie unter 2.1 funktioniert haben, denn das Prinzip der Pipelines gibt es seit OWIN - und das ist so ca. 8 Jahre her.

Doch. Ich hab es doch vor mir. Der Unterschied ist, dass kein Eintrag für


app.UseAuthorization();

existiert. Sondern nur Authentication.

Eines der größten Lücken war eine fehlerhafte Implementierung einer eigenen AuthorizeAttribute-Ableitung.

Das ist ja etwas, was ich nicht tue. Ich nutze ja das Standard Authorize Attribute.

Zum Abschluss. Danke für den Input 😃

16.806 Beiträge seit 2008
vor 4 Jahren

Die Authentifizierung habe ich genau so in einem Asp.Net Core 2.1 Projekt

Der Unterschied ist, dass kein Eintrag für app.UseAuthorization(); existiert. Sondern nur Authentication.

Kannst jetzt drehen und wenden wie Du willst; aber wenn Du was hinzufügst, dann war der Code eben nicht genau so. 👍

D
Duesmannr Themenstarter:in
161 Beiträge seit 2017
vor 4 Jahren

Ja da hast du Recht..^^