Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Content of created CookieAuthentication cookies?

Short version: Is there a way to determine the content of a cookie that’s created by the CookieAuthentication mechanism in .net-Core-2

Context:
I’m currently working on making a site GDPR compliant, among other things this means documenting all the cookies used and set on our site. We use asp.net core 2 and Identity to handle user log in and log out. The problem is all the cookies sett by identity and how to document them. Cookie 1.

  • AspNetCore.Identity.Application

This cookie is straight forward, it contains login information, privileges etc. etc. If you delete it, you will be logged out from our site. It’s critical to our application and thereby nothing to worry about.

The problem is the following cookie (Note. it’s one cookie split into three parts)

  • AspNetCore.Cookies
  • AspNetCore.CookiesC1
  • AspNetCore.CookiesC2

It's used and set by the cookieAuthentication but dont realy seem to do anything. You can delete them from the browser and still use the site seemingly fine. Making changes and save i for example, and ou wont be forcefully logged out.

This poses a problem because according to the new GDPR law, if these cookie chunks are optional the user should be able to deactivate them. (A problem for later)

The question then becomes: How can I find out what this cookie does and what information it contains. Both are equally important to find out.

Tried methods: I’ve tried to look through the source code to find answers, but the project development has been on and off for the last 2 years and is thereof quite the patchwork. I’ve also tried to decrypt the cookie using this post How to manually decrypt an ASP.NET Core Authentication cookie? as guide, but I’ve only been developing in C# for less than 4 months so I could not make it work since it seems to target an older/different version of .net-core.

Note! Since this is my first question on stack I welcome suggestions and tips on how I can make my question clearer or more useful to the general populace of this site.

Edit_1: Well there realy is'nt much config in the startupfile

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AutomaticAuthenticate = true
});

There is some code to enable OpenId with microsoft. But would that be put in this cookie?

// Configure the OWIN Pipeline to use OpenId Connect Authentication
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
    ClientId = Configuration["ClientId"],
    Authority = string.Format(Configuration["AadInstance"], 
    Configuration["Tenant"]),
    PostLogoutRedirectUri = Configuration["PostLogoutRedirectUri"],
    ClientSecret = Configuration["ClientSecret"],
    TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false
    },
    Events = new OpenIdConnectEvents
    {
        OnAuthenticationFailed = OnAuthenticationFailed,
        OnRedirectToIdentityProvider = (context) =>
        {
            if (context.HttpContext.Request.Query.ContainsKey("domain"))
            {
                context.ProtocolMessage.DomainHint = 
                    context.HttpContext.Request.Query["domain"];
            }

        return Task.FromResult(0);
        },
        OnTokenValidated = OnTokenValidated
    }
});

edit_2: Removed DI and misc logging but this is basicaly the whole file

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Serialization;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

public class Startup
{
    public UserManager<ApplicationUser> UserManager { get; set; }
    public SignInManager<ApplicationUser> SignInManager { get; set; }
    public IConfigurationRoot Configuration { get; set; }

    public Startup(IHostingEnvironment env)
    {
        // Set up configuration sources.
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .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<Startup>();
        }

        builder.AddEnvironmentVariables();
        Configuration = builder.Build();
    }


    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Adds a default in-memory implementation of IDistributedCache
        services.AddMemoryCache();

        services.AddSession();

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("DefaultConnection")));

        services.AddLocalization(options => options.ResourcesPath = "Resources");

        services.AddIdentity<ApplicationUser, IdentityRole>(o => {
            // configure identity options
            o.Password.RequireDigit = true;
            o.Password.RequireLowercase = true;
            o.Password.RequireUppercase = true;
            o.Password.RequireNonAlphanumeric = false;
            o.Password.RequiredLength = 8;
        })
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.AddMvc()
            .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, options => options.ResourcesPath = "Resources")
            .AddDataAnnotationsLocalization()
            .AddJsonOptions(options =>
            {
                options.SerializerSettings.ContractResolver = new DefaultContractResolver();
            });

        // OpenID Connect Authentication Requires Cookie Auth
        services.AddAuthentication(options =>
        {
            options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public async void Configure(
        IApplicationBuilder app,
        IHostingEnvironment env,
        ILoggerFactory loggerFactory,
        SampleDataInitializer sampleData,
        SeedData seedData)
    {
        app.UseSession();

        if (env.IsDevelopment())
        {
            app.UseBrowserLink();
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();

            try
            {
                using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
                    .CreateScope())
                {
                    serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
                            .Database.Migrate();
                }
            }
            catch { }
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");

            // For more details on creating database during deployment see http://go.microsoft.com/fwlink/?LinkID=615859
            try
            {
                using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
                    .CreateScope())
                {
                    serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
                            .Database.Migrate();
                }
            }
            catch { }
        }

        app.UseStaticFiles();
        app.UseIdentity();

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AutomaticAuthenticate = true
        });

        // Configure the OWIN Pipeline to use OpenId Connect Authentication
        app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
        {
            ClientId = Configuration["ClientId"],
            Authority = string.Format(Configuration["AadInstance"], Configuration["Tenant"]),
            PostLogoutRedirectUri = Configuration["PostLogoutRedirectUri"],
            ClientSecret = Configuration["ClientSecret"],
            TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = false
            },
            Events = new OpenIdConnectEvents
            {
                OnAuthenticationFailed = OnAuthenticationFailed,
                OnRedirectToIdentityProvider = (context) =>
                {
                    if (context.HttpContext.Request.Query.ContainsKey("domain"))
                    {
                        context.ProtocolMessage.DomainHint = context.HttpContext.Request.Query["domain"];
                    }

                    return Task.FromResult(0);
                },
                OnTokenValidated = OnTokenValidated
            }

        });

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

        await seedData.SeedBuyOutPoliciesForDepartments();
    }


    private async Task<Task> OnTokenValidated(TokenValidatedContext context)
    {
        try
        {
            // Get tenant Id from Claims
            var claim = context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid");

            if (claim?.Value == null)
            {
                context.HttpContext.Session.SetError("Not able to access tenant id from claim");
                return Task.FromResult(true);
            }

            var tenantId = claim.Value;

            var roleClaims = context.Ticket.Principal.Claims.Where(c => c.Type.ToLower().EndsWith("identity/claims/role")).ToList();

            //// Get group claims from logged in user
            var groupClaims = context.Ticket.Principal.Claims.Where(c => c.Type.ToLower() == "groups").ToList();

            var checkClaim = context.HttpContext.Session.GetString("CheckClaim");

            if (!string.IsNullOrEmpty(checkClaim))
            {
                context.HttpContext.Session.Remove("CheckClaim");

                var claimInfo = "";

                foreach (var currentUserClaim in context.Ticket.Principal.Claims)
                {
                    claimInfo += $"Type: {currentUserClaim.Type} Value: {currentUserClaim.Value} <br/>";
                }

                // Add groups to claimInfo string
                claimInfo += "<br/>Groups: <br/>";
                foreach (var groupClaim in groupClaims)
                {
                    claimInfo += $"{groupClaim.Value}, ";
                }

                // Add role(s) to claimInfo string
                claimInfo += "<br />Roles: <br />";
                foreach (var roleClaim in roleClaims)
                {
                    claimInfo += $"{roleClaim.Value}, ";
                }

                context.HttpContext.Session.SetString("Claim", claimInfo);

                return Task.FromResult(true);
            }

            var applicationDbContext = context.HttpContext.RequestServices.GetService<ApplicationDbContext>();

            // Check that tenant id exist in database
            var customer = applicationDbContext.Customers.Include(c => c.Departments).FirstOrDefault(x => x.TenantId == tenantId);
            if (customer == null)
            {
                context.HttpContext.Session.SetError("Your company is not configured to use log in with work account");
                return Task.FromResult(true);
            }

            // Used to find user in database or create new user
            var userManager = context.HttpContext.RequestServices.GetService<UserManager<ApplicationUser>>();

            //// Used to update user role
            var roleManager = context.HttpContext.RequestServices.GetService<RoleManager<IdentityRole>>();

            // Get email adress from AD user
            var email = context.Ticket.Principal.Identity.Name;

            // Check if user exist in database
            var user = await applicationDbContext.Users.Include(u => u.Department).FirstOrDefaultAsync(u => u.Email == email);


            if (user == null)
            {
                if (!customer.AllowCreateUserOnSignIn)
                {
                    context.HttpContext.Session.SetError(
                        "Your user does not exist in the portal and your company is not configured to allow creation of user on sign in. Please contact your administrator.", customer.ErrorText);

                    return Task.FromResult(true);
                }

                // Create new user with AdUser property set to true
                var givenName =
                    context.Ticket.Principal.Claims.FirstOrDefault(
                        x => x.Type.ToLower().Contains("givenname"));
                var surName =
                    context.Ticket.Principal.Claims.FirstOrDefault(
                        x => x.Type.ToLower().Contains("surname"));

                user = new ApplicationUser
                {
                    AdUser = true,
                    UserName = email,
                    Email = email,
                    FirstName = givenName != null ? givenName.Value : "",
                    LastName = surName != null ? surName.Value : "",
                };

                if (user.DepartmentId == null)
                {
                    user.DepartmentId = customer.Departments.FirstOrDefault(d => d.Default)?.Id;
                }

                // Add user to database
                var result = await userManager.CreateAsync(user);

                if (!result.Succeeded)
                {
                    context.HttpContext.Session.SetError(
                        "Your user does not exist in the portal. Please contact your administrator", customer.ErrorText);
                    return Task.FromResult(true);
                }
            }
            else
            {
                //Not allowed to login if user is deactivated/resigned
                if (user.ResignedDate != null)
                {
                    context.HttpContext.Session.SetError("Your user is deactivated. Please contact your administrator", customer.ErrorText);
                    return Task.FromResult(true);
                }
            }

            // Compare tenant id with user tenant
            var userCustomer = await applicationDbContext.Customers.FirstOrDefaultAsync(c => c.Id == user.Department.CustomerId);
            if (userCustomer == null || userCustomer.TenantId != tenantId)
            {
                context.HttpContext.Session.SetError("Your user does not exist in the portal. Please contact your administrator", customer.ErrorText);
                return Task.FromResult(true);
            }

            // Sign in user
            var signInManager = context.HttpContext.RequestServices.GetService<SignInManager<ApplicationUser>>();
            await signInManager.SignInAsync(user, true);
        }
        catch (Exception ex)
        {
            context.HttpContext.Session.SetError("Sorry, an error occured during log in. Please contact your administrator");
        }

        return Task.FromResult(0);
    }

    // Entry point for the application.
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }
}
like image 824
Dotten Avatar asked Dec 08 '25 20:12

Dotten


1 Answers

You get a chunked cookie if the Principal that is being stored is too big for a single cookie (think lots of roles, or claims). Cookies have a limited size, so chunking will kick in to allow full serialization of the principal.

When it comes to using OAuth/OIDC/WS-Fed in ASP.NET Core, yes, generally it gets serialized into a cookie authentication cookie in order for it to be saved across requests. OAuth can dumb a huge amount of claims into its results, so you can get up with chunked cookies.

So it's really one big cookie, in multiple parts, it's all the same cookie. So they're not optional when it comes to your site functioning as expected. (This is not legal advice for GDPR, that needs to come from an actual lawyer).

As an aside ASP.NET Core 2.1 comes with support for optional cookies, you can mark a cookie as required or not (Cookie authentication marks its cookies as required) and ASP.NET will not write optional cookies unless a user has consented to their use.

like image 182
blowdart Avatar answered Dec 10 '25 10:12

blowdart



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!