ASP.NET les Cookies SessionId + OWIN ne sont pas envoyés au navigateur

j'ai un étrange problème avec L'utilisation de L'authentification de cookie Owin.

quand je démarre mon authentification de serveur IIS fonctionne parfaitement bien sur IE/Firefox et Chrome.

j'ai commencé à faire des tests D'authentification et de connexion sur différentes plateformes et j'ai trouvé une erreur étrange. De manière sporadique, Owin framework / IIS n'envoie tout simplement pas de cookies aux navigateurs. Je vais taper un nom d'utilisateur et un mot de passe qui est correct le code s'exécute mais aucun cookie n'est livré au navigateur. Si je redémarre le serveur, il commence à fonctionner, puis à un moment donné, je vais essayer de connexion et à nouveau les cookies arrêter d'être livrés. Passer au-dessus du code ne fait rien et ne jette aucune erreur.

 app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationMode = AuthenticationMode.Active,
            CookieHttpOnly = true,
            AuthenticationType = "ABC",
            LoginPath = new PathString("/Account/Login"),
            CookiePath = "/",
            CookieName = "ABC",
            Provider = new CookieAuthenticationProvider
               {
                  OnApplyRedirect = ctx =>
                  {
                     if (!IsAjaxRequest(ctx.Request))
                     {
                        ctx.Response.Redirect(ctx.RedirectUri);
                     }
                 }
               }
        });

et dans ma procédure de connexion j'ai le code suivant:

IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
                            authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

var authentication = HttpContext.Current.GetOwinContext().Authentication;
var identity = new ClaimsIdentity("ABC");
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.User_ID.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Role, role.myRole.ToString()));
    authentication.AuthenticationResponseGrant =
        new AuthenticationResponseGrant(identity, new AuthenticationProperties()
                                                   {
                                                       IsPersistent = isPersistent
                                                   });

authenticationManager.SignIn(new AuthenticationProperties() {IsPersistent = isPersistent}, identity);

mise à Jour 1: Il semble que l'une des causes du problème, c'est quand je ajouter des éléments à la session les problèmes commencent. Ajouter quelque chose de simple comme Session.Content["ABC"]= 123 semble créer le problème.

Ce que je peux faire est comme suit: 1) (Chrome) quand je me connecte je reçois ASP.NET_SessionId + mon cookie d'authentification. 2) je vais à une page qui définit une session.contenu... 3) Ouvrez un nouveau navigateur (Firefox) et essayez login et il ne reçoit pas un ASP.NET_SessionId ne reçoit pas non plus de Cookie D'authentification 4) tandis que le premier navigateur a L'ASP.NET_SessionId it continue à travailler. Dès que je supprime ce cookie, il a le même problème que tous les autres navigateurs Je suis en train de travailler sur l'adresse ip (10.x.x.x) et localhost.

mise à Jour 2: Forcer la création de ASPNET_SessionId d'abord sur mon login_load page avant de l'authentification avec OWIN.

1) avant de m'authentifier avec OWIN, je fais une valeur aléatoire Session.Content sur ma page de connexion pour lancer L'ASP.NET_SessionId 2) ensuite, je authentifier et de la poursuite de session 3) D'autres navigateurs semblent maintenant fonctionner

c'est bizarre. Je ne peux que conclure que cela a quelque chose à voir avec ASP et OWIN pensant qu'ils sont dans des domaines différents ou quelque chose comme ça.

Update 3 - comportement étrange entre les deux.

comportement étrange supplémentaire identifié-le temps D'arrêt de la session Owin et ASP est différent. Ce que je vois c'est que mes sessions Owin restent en vie plus longtemps que mes sessions ASP par un mécanisme quelconque. Donc, lors de la connexion: 1.) J'ai un cookied en fonction d'authentification de session 2.) J'ai mis un peu de variables de session

mes variables de session (2) "meurent" avant que la variable owin cookie session ne force la reconnexion, ce qui provoque un comportement inattendu dans toute mon application. (La personne est connecté, mais n'est pas vraiment connecté)

"1519130920 de mise à Jour" 3B

après quelques fouilles j'ai vu certains commentaires sur une page qui dit que le délai d'authentification "forms" et le temps d'attente de session doivent concorder. Je pense que normalement les deux sont synchrones, mais pour une raison quelconque, les deux ne sont pas synchrones.

Résumé des Solutions de contournement

1) Créez toujours une Session avant l'authentification. Essentiellement créer session lorsque vous commencez l'application Session["Workaround"] = 0;

2) [expérimental] si vous persistez cookies faire assurez - vous que votre délai / longueur OWIN est plus long que votre sessionTimeout dans votre web.config (dans le test)

123
demandé sur Dan Homola 2013-12-23 09:39:34

8 réponses

j'ai rencontré le même problème et j'ai tracé la cause à OWIN ASP.NET mise en œuvre de l'hébergement. Je dirais que c'est un bug.

de fond

mes conclusions sont basées sur ces versions d'assemblage:

  • Microsoft.Owin, Version=2.0.2.0, Culture=neutre, PublicKeyToken=31bf3856ad364e35
  • Microsoft.Owin.Hôte.SystemWeb, Version=2.0.2.0, Culture = neutre, Publiceytoken=31bf3856ad364e35
  • système.Web, Version=4.0.0.0, Culture=neutre, PublicKeyToken=b03f5f7f11d50a3a

OWIN utilise sa propre abstraction pour travailler avec les Cookies de réponse ( Microsoft.Owin.Responsabilecookiecollection ). Cette implémentation enveloppe directement la collection des en-têtes de réponse et met donc à jour l'en-tête Set-Cookie . OWIN ASP.NET hôte ( Microsoft.Owin.Hôte.SystemWeb ) encapsule du Système.Web.HttpResponse et c'est collection headers. Ainsi, lorsque le nouveau cookie est créé par OWIN, l'en-tête Set-Cookie est modifié directement.

But ASP.NET utilise également sa propre abstraction pour travailler avec les Cookies de réponse. Ceci nous est exposé comme système .Web.HttpResponse.Cookies propriété et mis en œuvre par classe scellée Système.Web.HttpCookieCollection . Cette implémentation n'enroule pas la réponse Set-Cookie en-tête directement mais utilise quelques optimisations et quelques notifications internes pour manifester son changement d'état en objet de réponse.

puis il y a un point de retard dans la durée de vie de la requête où HttpCookieCollection change d'état est testé ( système.Web.HttpResponse.Generateresponses headersforcookies() ) et les cookies sont sérialisés en Set-Cookie en-tête. Si cette collection est dans un état spécifique, L'en-tête Set-Cookie est d'abord effacé et recréé à partir des cookies stockés dans collection.

ASP.NET la mise en œuvre de la session utilise le système .Web.HttpResponse.Cookies propriété pour stocker son ASP.Cookie NET_SessionId. Il y a aussi une optimisation de base dans ASP.NET module d'état de session ( Système.Web.SessionState.SessionStateModule ) implémenté par la propriété statique s_sessioneverset qui est assez explicite. Si jamais vous stockez quelque chose à l'état de session dans votre application, ce module fera un peu plus de travail pour chaque demande.


retour à notre problème de login

avec tous ces éléments vos scénarios peuvent être expliqués.

Cas 1 - Session n'a jamais défini

du Système.Web.SessionState.SessionStateModule , s_sessioneverset propriété est fausse. Aucun id de session n'est généré par le module d'état de session et le système .Web.HttpResponse.Cookies l'état de collecte est non détecté en tant que modifié . Dans ce cas, les cookies OWIN sont envoyés correctement au navigateur et les travaux de connexion.

Cas 2-Session a été utilisé quelque part dans l'application, mais pas avant que l'utilisateur tente d'authentifier

du Système.Web.SessionState.SessionStateModule , s_sessioneverset propriété est vrai. Les Id de Session sont générés par SessionStateModule , ASP.NET_SessionId est ajouté au système .Web.HttpResponse.Cookies collection mais il est supprimé plus tard dans la vie de la demande que la session de l'utilisateur est en fait vide. Dans ce cas système.Web.HttpResponse.Cookies l'état de collecte est détecté comme modifié et Set-Cookie l'en-tête est d'abord effacé avant que les cookies sont sérialisés à la valeur de l'en-tête.

dans ce cas, les cookies de réponse OWIN sont" perdus " et l'utilisateur n'est pas authentifié et est redirigé vers la page de connexion.

cas 3-la Session est utilisée avant que l'utilisateur ne tente de s'authentifier

du Système.Web.SessionState.SessionStateModule , s_sessioneverset propriété est vrai. Les Id de Session sont générés par SessionStateModule , ASP.NET_SessionId est ajouté au système .Web.HttpResponse.Cookies . En raison de l'optimisation interne dans le système .Web.HttpCookieCollection et système.Web.HttpResponse.Generateresponses headersforcookies() Set-l'en-tête de Cookie n'est pas d'abord effacé mais seulement mis à jour.

dans ce cas les cookies D'authentification OWIN et ASP.Les cookies NET_SessionId sont envoyés en réponse et le login fonctionne.


plus général problème avec les cookies

comme vous pouvez le voir le problème est plus général et non limité à ASP.NET session. Si vous hébergez OWIN par Microsoft.Owin.Hôte.SystemWeb et vous/quelque chose utilise directement le système .Web.HttpResponse.Cookies collection vous êtes à risque.

par exemple cela fonctionne et les deux cookies sont correctement envoyés au navigateur...

public ActionResult Index()
{
    HttpContext.GetOwinContext()
        .Response.Cookies.Append("OwinCookie", "SomeValue");
    HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";

    return View();
}

mais ce n'est pas et OwinCookie est"perdu"...

public ActionResult Index()
{
    HttpContext.GetOwinContext()
        .Response.Cookies.Append("OwinCookie", "SomeValue");
    HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";
    HttpContext.Response.Cookies.Remove("ASPCookie");

    return View();
}

tous les deux testés à partir de VS2013, IISExpress et par défaut Modèle de projet MVC.

142
répondu Tomas Dolezal 2015-03-25 21:35:40

en commençant par la grande analyse de @TomasDolezal, j'ai regardé à la fois L'Owin et le système.Web source.

le problème est ce système.Le Web a sa propre source principale d'informations sur les cookies et ce n'est pas L'en-tête Set-Cookie. Owin ne connaît que L'en-tête des cookies. Une solution consiste à s'assurer que tous les cookies définis par Owin sont également définis dans la collection HttpContext.Current.Response.Cookies .

j'ai fait un petit middleware ( source , nuget ) qui fait exactement cela, qui est destiné à être placé immédiatement au-dessus de l'enregistrement middleware cookie middleware.

app.UseKentorOwinCookieSaver();

app.UseCookieAuthentication(new CookieAuthenticationOptions());
38
répondu Anders Abel 2014-12-01 08:58:29

en bref, le gestionnaire de cookies .NET va gagner sur le gestionnaire de cookies OWIN et écraser les cookies définis sur la couche OWIN . Le correctif est d'utiliser la classe SystemWebCookieManager, fournie comme solution sur le projet Katana ici . Vous devez utiliser cette classe ou une similaire, qui va forcer OWIN à utiliser le gestionnaire de cookies .NET afin qu'il n'y ait pas d'incohérences :

public class SystemWebCookieManager : ICookieManager
{
    public string GetRequestCookie(IOwinContext context, string key)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
        var cookie = webContext.Request.Cookies[key];
        return cookie == null ? null : cookie.Value;
    }

    public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);

        bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
        bool pathHasValue = !string.IsNullOrEmpty(options.Path);
        bool expiresHasValue = options.Expires.HasValue;

        var cookie = new HttpCookie(key, value);
        if (domainHasValue)
        {
            cookie.Domain = options.Domain;
        }
        if (pathHasValue)
        {
            cookie.Path = options.Path;
        }
        if (expiresHasValue)
        {
            cookie.Expires = options.Expires.Value;
        }
        if (options.Secure)
        {
            cookie.Secure = true;
        }
        if (options.HttpOnly)
        {
            cookie.HttpOnly = true;
        }

        webContext.Response.AppendCookie(cookie);
    }

    public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        AppendResponseCookie(
            context,
            key,
            string.Empty,
            new CookieOptions
            {
                Path = options.Path,
                Domain = options.Domain,
                Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            });
    }
}

dans votre démarrage de l'application, il suffit de l'assigner lorsque vous créez vos dépendances OWIN:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    ...
    CookieManager = new SystemWebCookieManager()
    ...
});

une réponse similaire a été fournie ici, mais elle n'inclut pas toute la base de code nécessaire pour résoudre le problème, donc je vois un besoin de l'ajouter ici parce que le lien externe au projet Katana peut descendre et cela devrait être entièrement chroniqué comme une solution ici aussi.

23
répondu Alexandru 2017-08-19 14:35:43

Katana équipe a répondu à la problème Tomas Dolezar soulevées, et publié de la documentation sur les solutions de contournement :

Solutions de contournement se répartissent en deux catégories. L'une consiste à reconfigurer Système.Web donc il évite d'utiliser la réponse.Collection de Cookies et l'écrasement de la OWIN cookies. L'autre approche consiste à reconfigurer les composants touchés OWIN afin qu'ils écrivent des cookies directement à Système.Web Réponse.La collecte des Cookies.

  • S'assurer que la session est établie avant l'authentification: le conflit entre les systèmes.Cookies Web et Katana est par demande, il peut donc être possibilité pour la demande d'établir la session sur demande avant le processus d'authentification. Cela devrait être facile à faire lorsque le l'utilisateur arrive d'abord, mais il peut être plus difficile de garantir plus tard, lorsque le les cookies de session ou d'autorisation expirent et / ou doivent être rafraîchis.
  • désactiver le module SessionStateModule-si l'application ne se base pas sur les informations de session, mais que le module de session est encore en train de définir un cookie qui provoque le conflit ci-dessus, alors vous pouvez envisager de désactiver le module d'état de session.
  • Reconfigure le CookieAuthenticationMiddleware pour écrire directement au système.La collection de cookies de Web.
app.UseCookieAuthentication(new CookieAuthenticationOptions
                                {
                                    // ...
                                    CookieManager = new SystemWebCookieManager()
                                });

Voir SystemWebCookieManager mise en œuvre de la documentation (lien ci-dessus)

plus d'information ici

Modifier

ci-dessous les mesures que nous avons prises pour résoudre le problème. Les deux 1. et 2. nous avons également résolu le problème séparément, mais nous avons décidé d'appliquer les deux juste au cas où:

1. Use SystemWebCookieManager 151990920"

2. Définir la variable de session:

protected override void Initialize(RequestContext requestContext)
{
    base.Initialize(requestContext);

    // See /q/asp-net-sessionid-owin-cookies-do-not-send-to-browser-68173/"FixEternalRedirectLoop"] = 1;
}

(note latérale: la méthode D'initialisation ci-dessus est l'endroit logique pour le repère parce que la base.Initialize rend la Session disponible. Cependant, le correctif pourrait aussi être appliqué plus tard car dans OpenId il y a d'abord une requête anonyme, puis une redirection vers le fournisseur OpenId et ensuite de nouveau vers l'application. Les problèmes se produiraient après la redirection vers l'application alors que le correctif définit la variable session déjà lors de la première requête anonyme réparant ainsi le problème avant toute redirection de retour arrive même)

Edit 2

Copier-coller à partir de la Katana projet 2016-05-14:

ajouter ce qui suit:

app.UseCookieAuthentication(new CookieAuthenticationOptions
                                {
                                    // ...
                                    CookieManager = new SystemWebCookieManager()
                                });

...et ceci:

public class SystemWebCookieManager : ICookieManager
{
    public string GetRequestCookie(IOwinContext context, string key)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
        var cookie = webContext.Request.Cookies[key];
        return cookie == null ? null : cookie.Value;
    }

    public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);

        bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
        bool pathHasValue = !string.IsNullOrEmpty(options.Path);
        bool expiresHasValue = options.Expires.HasValue;

        var cookie = new HttpCookie(key, value);
        if (domainHasValue)
        {
            cookie.Domain = options.Domain;
        }
        if (pathHasValue)
        {
            cookie.Path = options.Path;
        }
        if (expiresHasValue)
        {
            cookie.Expires = options.Expires.Value;
        }
        if (options.Secure)
        {
            cookie.Secure = true;
        }
        if (options.HttpOnly)
        {
            cookie.HttpOnly = true;
        }

        webContext.Response.AppendCookie(cookie);
    }

    public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        AppendResponseCookie(
            context,
            key,
            string.Empty,
            new CookieOptions
            {
                Path = options.Path,
                Domain = options.Domain,
                Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            });
    }
}
14
répondu karl25233 2017-03-16 16:25:31

les réponses ont déjà été fournies, mais dans owin 3.1.0, il y a une classe SystemWebChunkingCookieManager qui peut être utilisée.

https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs

https://raw.githubusercontent.com/aspnet/AspNetKatana/c33569969e79afd9fb4ec2d6bdff877e376821b2/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    ...
    CookieManager = new SystemWebChunkingCookieManager()
    ...
});
3
répondu jonmeyer 2017-12-06 00:23:27

si vous installez vous-même des cookies dans OWIN middleware, l'utilisation de OnSendingHeaders semble contourner le problème.

par exemple, en utilisant le code ci-dessous owinResponseCookie2 sera défini, même si owinResponseCookie1 n'est pas:

private void SetCookies()
{
    var owinContext = HttpContext.GetOwinContext();
    var owinResponse = owinContext.Response;

    owinResponse.Cookies.Append("owinResponseCookie1", "value1");

    owinResponse.OnSendingHeaders(state =>
    {
        owinResponse.Cookies.Append("owinResponseCookie2", "value2");
    },
    null);

    var httpResponse = HttpContext.Response;
    httpResponse.Cookies.Remove("httpResponseCookie1");
}
2
répondu Appetere 2016-03-22 11:59:18

Le plus rapide de la ligne de solution de code:

HttpContext.Current.Session["RunSession"] = "1";

il suffit d'ajouter cette ligne avant la méthode CreateIdentity:

HttpContext.Current.Session["RunSession"] = "1";
var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
_authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = rememberLogin }, userIdentity);
1
répondu Alexander Trofimov 2017-07-26 03:30:19

j'ai eu le même symptôme de L'absence d'envoi de L'en-tête de L'ensemble-Cookie, mais aucune de ces réponses ne m'a aidé. Tout fonctionnait sur ma machine locale, mais lorsqu'elle était déployée à la production, les en-têtes de Set-cookie ne se réglaient jamais.

il s'avère que c'était une combinaison de l'utilisation d'une coutume CookieAuthenticationMiddleware avec WebApi avec support de compression WebApi

heureusement que J'utilisais ELMAH dans mon projet qui m'a permis à cette exception en cours de la session:

Système

.Web.Le serveur HttpException ne peut pas ajouter d'en-tête après HTTP les en-têtes ont été envoyés.

qui m'a conduit à ce Github Issue

essentiellement, si vous avez une configuration étrange comme la mienne, vous voudrez désactiver la compression pour vos contrôleurs WebApi / méthodes qui fixent les cookies, ou essayer le OwinServerCompressionHandler .

0
répondu Hugh Jeffner 2016-05-06 17:43:29