ResetPassword Token How and where is it stored? - asp.net

I've been trying to understand how the reset password & account confirmation works in ASP.NET Identity. I'd just like to know if the Tokens are being stored and if so, where?
The links I receive when I'm using the password reset feature look something like this
http://localhost:1470/Account/ResetPassword?userId=a8b1389c-df93-4dfc-b463-541507c1a4bc&code=yhUegXIM9SZBpPVbBtv22kg7NO7F96B8MJi9MryAadUY5XYjz8srVkS5UL8Lx%2BLPYTU6a6jhqOrzMUkkMyPbEHPY3Ul6%2B%2F0s0qQvtM%2FLLII3s29FgkcK0OnjX46Bmj9JlFCUx53rOH%2FXMacwnKDzoJ1rbrUyypZiJXloIE50Q6iPuMTUHbX9O%2B3JMZtCVXjhhsHLkTOn9IVoN6uVAOMWNQ%3D%3D
My guess is that the tokens are stored in the link itself since I cannot find any trace of it anywhere else. Maybe someone knows for sure?

As I mentioned in the comment
"Tokens are generated using the SecurityStamp and validating against the SecurityStamp and not storing anywhere in database or local file storage. If you update the SecurityStamp, then previous tokens are no longer valid."

#DSR is correct but I would like to add some information to this as well.
If you have set up a Web project with Individual User Accounts go to:
App_Start -> IdentityConfig.cs
There you will see code like this:
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
The description for DataProtectorTokenProvider<TUser, TKey> gives the information:
Represents a token provider that uses an IDataProtector to generate
encrypted tokens based off of the security stamp.
https://learn.microsoft.com/en-us/previous-versions/aspnet/dn613280(v%3dvs.108)
We can however try to dig a bit deeper how it really works. The token verification will fail if different Application Pool Identities are used for creating and validating a token on a single server. This points to that the actual protection mechanism would look something like this:
System.Security.Cryptography.ProtectedData.Protect(userData, entropy, DataProtectionScope.CurrentUser);
Given that it works if all sites use the same Application Pool Identity points to this as well. Could also be DataProtectionProvider with protectionDescriptor "LOCAL=user". It should have worked with different Application Pool Identities if LOCAL=machine was set.
new DataProtectionProvider("LOCAL=user")
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotector?view=netframework-4.7.2
https://learn.microsoft.com/en-us/uwp/api/windows.security.cryptography.dataprotection.dataprotectionprovider
dataProtectionProvider is of type IDataProtectionProvider.
It is injected in Startup.Auth.cs like this:
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
CreatePerOwinContext is located in the assembly Microsoft.AspNet.Identity.Owin -> AppBuilderExtensions.cs. Both ASP.NET Identity and ASP.NET Core Identity are open source and can be viewed at GitHub.
public static IAppBuilder CreatePerOwinContext<T>(this IAppBuilder app,
Func<IdentityFactoryOptions<T>, IOwinContext, T> createCallback,
Action<IdentityFactoryOptions<T>, T> disposeCallback) where T : class, IDisposable
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (createCallback == null)
{
throw new ArgumentNullException("createCallback");
}
if (disposeCallback == null)
{
throw new ArgumentNullException("disposeCallback");
}
app.Use(typeof (IdentityFactoryMiddleware<T, IdentityFactoryOptions<T>>),
new IdentityFactoryOptions<T>
{
DataProtectionProvider = app.GetDataProtectionProvider(),
Provider = new IdentityFactoryProvider<T>
{
OnCreate = createCallback,
OnDispose = disposeCallback
}
});
return app;
}
https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Owin/Extensions/AppBuilderExtensions.cs
https://archive.codeplex.com/?p=aspnetidentity#src/Microsoft.AspNet.Identity.Owin/Extensions/AppBuilderExtensions.cs
app.GetDataProtectionProvider() is in turn located in assembly Microsoft.Owin.Security that is also Open Source.
public static IDataProtectionProvider GetDataProtectionProvider(this IAppBuilder app)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
object value;
if (app.Properties.TryGetValue("security.DataProtectionProvider", out value))
{
var del = value as DataProtectionProviderDelegate;
if (del != null)
{
return new CallDataProtectionProvider(del);
}
}
return null;
}
https://github.com/aspnet/AspNetKatana/blob/release/src/Microsoft.Owin.Security/DataProtection/AppBuilderExtensions.cs
We can also see that CreateDataProtector has a fallback to the implementation DpapiDataProtectionProvider.
private static IDataProtectionProvider FallbackDataProtectionProvider(IAppBuilder app)
{
return new DpapiDataProtectionProvider(GetAppName(app));
}
When reading about DpapiDataProtectionProvider(DPAPI stands for Data Protection Application Programming Interface) the description says:
Used to provide the data protection services that are derived from the
Data Protection API. It is the best choice of data protection when you
application is not hosted by ASP.NET and all processes are running as
the same domain identity.
The Create method purposes are described as:
Additional entropy used to ensure protected data may only be
unprotected for the correct purposes.
The protector class itself then looks like this:
using System.Security.Cryptography;
namespace Microsoft.Owin.Security.DataProtection
{
internal class DpapiDataProtector : IDataProtector
{
private readonly System.Security.Cryptography.DpapiDataProtector _protector;
public DpapiDataProtector(string appName, string[] purposes)
{
_protector = new System.Security.Cryptography.DpapiDataProtector(appName, "Microsoft.Owin.Security.IDataProtector", purposes)
{
Scope = DataProtectionScope.CurrentUser
};
}
public byte[] Protect(byte[] userData)
{
return _protector.Protect(userData);
}
public byte[] Unprotect(byte[] protectedData)
{
return _protector.Unprotect(protectedData);
}
}
}
https://learn.microsoft.com/en-us/previous-versions/aspnet/dn253784(v%3dvs.113)

Related

deliver token manually (in addition to the owin configuration way)

I have actually an asp.net website application, that can deliver token to an user with the following way :
the user logs into the application, go to a specific page and obtains a clientid and a clientsecret.
then, he calls the following api "....api/token" by giving clientid and clientsecret (client credentials grant type) to get the token.
This is the associated code :
using Microsoft.Owin;
using Owin;
using System;
using Microsoft.Owin.Security.OAuth;
[assembly: OwinStartup(typeof(MyApp.Web.App_Start.OwinStartup))]
namespace MyApp.Web.App_Start
{
public class OwinStartup
{
public void Configuration(IAppBuilder app)
{
OwinWebApiStartup.Configuration(app);
}
}
}
public static class OwinWebApiStartup
{
public static void Configuration(IAppBuilder app)
{
var provider = //my provider implementation;
var oauthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20),
Provider = provider,
};
app.UseOAuthAuthorizationServer(oauthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
AccessTokenProvider = //my provider implementation,
});
}
}
This is working nicely. But I would like to add a new feature, where a javascript client code, not an user anymore, would like to call my apis, and so it will need to have a token, but do not have a clientid and clientsecret.
This is my idea :
Create a new api endpoint, (only this one will be reachable by my javascript client code without token, and there, the code will generate a token (thanks to the username of the current user connected) and return this one (that will
be the same that an user could have obtained with the existing method) to be used by the javascript client code
I faced this problem in the past. I solved this via querystring, cause owin could only provide one token ressource. In fact it makes sense to rely on owin and on not implementing your own code.
My pseudo solution:
KeyValuePair<string, string[]> typePair = ctx.Request.Query.FirstOrDefault(x => x.Key == "type");
LoginType? loginType = GetLoginType(typePair);
[...]
switch (loginType)
{
case LoginType.User:
[...]
////within this routine you could set your claims depending on your needs
If you get another solution, I'd be grateful for sharing

App Service to EntityFramework using MSI

I'm trying to retrofit MSI to an existing app.
The original app's DbContext used only a Constructor that found a ConnectionString by the same name in the web.config.
I've modified it to use a DbConnectionFactory to inject an AccessToken.
public class AppCoreDbContext : DbContext {
public AppCoreDbContext() : this("AppCoreDbContext")
{
}
public AppCoreDbContext(string connectionStringOrName) : base( OpenDbConnectionBuilder.Create(connectionStringOrName).Result, true)
{
}
...etc...
}
The class that it is invoking looks like:
public static class OpenDbConnectionBuilder
{
public static async Task<DbConnection> CreateAsync(string connectionStringName)
{
var connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName];
var dbConnection = DbProviderFactories
.GetFactory(connectionStringSettings.ProviderName)
.CreateConnection();
dbConnection.ConnectionString = connectionStringSettings.ConnectionString;
await AttachAccessTokenToDbConnection(dbConnection);
// Think DbContext will open it when first used.
//await dbConnection.OpenAsync();
return dbConnection;
}
static async Task AttachAccessTokenToDbConnection(IDbConnection dbConnection)
{
SqlConnection sqlConnection = dbConnection as SqlConnection;
if (sqlConnection == null)
{
return;
}
string msiEndpoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT");
if (string.IsNullOrEmpty(msiEndpoint))
{
return;
}
var msiSecret = Environment.GetEnvironmentVariable("MSI_SECRET");
if (string.IsNullOrEmpty(msiSecret))
{
return;
}
string accessToken = await AppCoreDbContextMSITokenFactory.GetAzureSqlResourceTokenAsync();
sqlConnection.AccessToken = accessToken;
}
}
Which invokes
// Refer to: https://winterdom.com/2017/10/19/azure-sql-auth-with-msi
public static class AppCoreDbContextMSITokenFactory
{
private const String azureSqlResource = "https://database.windows.net/";
public static async Task<String> GetAzureSqlResourceTokenAsync()
{
var provider = new AzureServiceTokenProvider();
var result = await provider.GetAccessTokenAsync(azureSqlResource);
return result;
}
}
The result of the above is that when tracking it with a debugger, it gets to
var result = await provider.GetAccessTokenAsync(azureSqlResource);
then hangs for ever.
Note: I'm working on a personal machine, not joined to the organisation domain -- but my personal MSA has been invited to the organisation's domain.
Admittedly, I've taken a hiatus from development for a couple of years, and the hang is probably due to having made a mistake around await (always been rough on understanding that implicitly)... but while trying to figure that out, and the documentation is pretty sparse, would appreciate feedback as to whether the above was the intended approach for using MSI.
I'm wondering:
When deploying to Azure, we can tell the ARM to create the Identity -- when developing, how do we tell the local machine to use MSI?
If on the dev machine the connection string is to a local db, and I create and add the token anyway, will it ignore it, or raise an exception.
This is a bit beyond the scope of discussing MSI, but I've never before created a dbConnection to use within a DbContext. Does anyone know the pros/cons of the DbContext 'owning' the connection? I'm assuming that it would be wiser to own & close the connection when the dbcontext is closed.
Basically...this is all new, so would appreciate any advice on getting this working -- the concept of being able to deploy without secrets would be awesome and would really like to get this demo working.
Thanks very much!
Hello user9314395: Managed Service Identity only works with resources running on Azure. While we don't support the local development scenario, you might consider looking into using the following (preview) library: https://learn.microsoft.com/en-us/azure/key-vault/service-to-service-authentication

Mocking Services in ASP.NET Core

I have a simple form to save and then use MailKit to provide email notification, with xUnit and Moq used for unit testing. I'm having difficulty setting up the unit test and associated services. I have a workaround ('if' statement in the action method) to only test the core repo saving functionality without also testing the email service. If I take out the if statement, the unit test does not have access to the appropriate methods, such as setting the web root path. The error is a null exception. If I default this value, there are other errors, such as "no database provider being configured for DbContext."
Is there a more appropriate way to set a unit test of this sort up? Or is it wrong to set up a unit test to test both the Create() and email functionality because it violates the one-function unit testing rule?
Unit test:
[Fact]
public void Can_Create_New_Lesson()
{
//Arrange
//create a mock repository
Mock<IHostingEnvironment> mockEnv = new Mock<IHostingEnvironment>();
Mock<ILessonRepository> mockRepo = new Mock<ILessonRepository>();
Mock<UserManager<AppUser>> mockUsrMgr = GetMockUserManager();
Mock<RoleManager<IdentityRole>> mockRoleMgr = GetMockRoleManager();
var opts = new DbContextOptions<AppIdentityDbContext>();
Mock <AppIdentityDbContext> mockCtx = new Mock<AppIdentityDbContext>(opts);
//create mock temporary data
Mock<ITempDataDictionary> tempData = new Mock<ITempDataDictionary>();
//create the controller
LessonController target = new LessonController(mockRepo.Object, mockEnv.Object, mockUsrMgr.Object, mockRoleMgr.Object, mockCtx.Object)
{
TempData = tempData.Object
};
//create a lesson
Lesson lesson = new Lesson { Title = "Unit Test", Domain= "Unit Test"};
//Act
//try to save the product using the Create method of the controller
IActionResult result = target.Create(lesson);
//Assert
//check that the repository was called
mockRepo.Verify(m => m.SaveLesson(lesson));
//check the result type is a redirection to the List action method of the controller
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Success", (result as RedirectToActionResult).ActionName);
}
The Create() action method:
[HttpPost]
public IActionResult Create(Lesson lesson)
{
if (ModelState.IsValid)
{
repository.SaveLesson(lesson);
//This IF statement is a workaround for the unit test
//don't email users if the Title is "Unit Test"
if (lesson.Title != "Unit Test")
{
emailUsers(lesson);
}
TempData["message"] = $"{lesson.Title} has been saved";
//show the user that the update was made successfully
return RedirectToAction("Success");
}
else
{
//there is a problem with the data values
return View(lesson);
}
}
Email function:
public void emailUsers(Lesson lesson)
{
var webRoot = environment.WebRootPath;
var filePath = System.IO.Path.Combine(webRoot, "email\\NewLessonSubmitted.txt");
string message = System.IO.File.ReadAllText(filePath);
string domain = lesson.Domain;
IQueryable<AppUser> userList = GetUsersInRole(identityContext, domain);
//if there are users in that domain, send the email
if (userList != null)
{
foreach (AppUser user in userList)
{
sendEmail(domain, message, user.Email);
}
}
}
EDIT: I've instead implemented the email service as a class, as pointed out by MotoSV. However, I'm still getting an error for "No database provider has been configured for this DbContext" The stack trace for the exception points to the following method:
public static IQueryable<AppUser> GetUsersInRole(AppIdentityDbContext db, string roleName)
{
if (db != null && roleName != null)
{
var roles = db.Roles.Where(r => r.Name == roleName);
if (roles.Any())
{
var roleId = roles.First().Id;
return from user in db.Users
where user.Roles.Any(r => r.RoleId == roleId)
select user;
}
}
return null;
}
I have this constructor in my dbContext class:
public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options)
: base(options) { }
EDIT: The solution (provided by MotoSV) was to:
1) Create an email service class with appropriate methods and
2) Install the appropriate Nuget package for Microsoft.EntityFrameworkCore.InMemory
3) mock the DbContext as:
var opts = new DbContextOptionsBuilder<AppIdentityDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
Mock<AppIdentityDbContext> mockCtx = new Mock<AppIdentityDbContext>(opts);
I would look at moving the code responsible for sending emails into it's own class. This class will implement an interface that can then be mocked in your test.
For example, create the interface and implementation:
public interface IEmailService
{
void SendEmail(string to, string from, string body);
}
public class EmailService : IEmailService
{
public void SendEmail(string to, string from string body)
{
...
}
}
The EmailService class will hold the functionality required to talk to MailKit. Then, register the IEmailService with .NET Core and add it to the constructor of your class:
public class LessonController : Controller
{
private readonly IEmailService _emailService;
public LessonController(IEmailService service, ...)
{
_emailService = emailService;
}
public void emailUsers(Lessong lesson)
{
...
if(userList != null)
{
foreach(...)
{
_emailService.Send(...);
}
}
...
}
}
In your test create a mock and pass that into your constructor.
First and foremost, you should never do stuff like putting in conditionals in your code for the purpose of unit testing. If for no other reason, you're violating the entire point of unit testing, as your test access different code paths than what your users actually experience; you learn nothing by doing this.
Testing that the repo actually saves is a job for a repo test not an action test. Likewise with your mail service: ensuring that an email is actually sent should be a test on your mail service, not your action method.
Long and short, your test here should simply ensure that the appropriate actions are taken (i.e. repo save is hit and email service send is hit). As such, you can drop in simple mocks that merely have those methods available to be hit. You don't need to (and shouldn't) be establishing full connections to the DB/SMTP server, as at that point you're integration testing, not unit testing.
Your applications send email class constructor should take an "email provider" object that is a generic email abstraction based on an IEmailProvider interface, and/or also take a IDataAccessProvider implementation.
Now you can mock both of these interfaces in the test and pass them to the send email class to test just your implementation.

DNX Core: Encrypt/Decrypt?

I'm porting a website to dnx core/aspnet5/mvc6. I need to store passwords to 3rd party sites in the database (it's essentially an aggregator).
In earlier versions of mvc, I did this using classes like RijndaelManaged. But those don't appear to exist in dnx core. In fact, I haven't been able to find much documentation on any general purpose encryption/decryption stuff in dnx core.
What's the recommended approach for encrypting/decrypting single field values in an mvc6 site? I don't want to encrypt the entire sql server database.
Or should I be looking at a different approach for storing the credentials necessary to access a password-protected 3rd party site?
See the DataProtection API documentation
Their guidance on using it for persistent data protection is a little hedgy but they say there is no technical reason you can't do it. Basically to store protected data persistently you need to be willing to allow unprotecting it with expired keys since the keys could expire after you protect it.
To me it seems reasonable to use it and I am using it in my own project.
Since the IPersistedDataProtector only provides methods with byte arrays I made a couple of extension methods to convert the bytes back and forth from string.
public static class DataProtectionExtensions
{
public static string PersistentUnprotect(
this IPersistedDataProtector dp,
string protectedData,
out bool requiresMigration,
out bool wasRevoked)
{
bool ignoreRevocation = true;
byte[] protectedBytes = Convert.FromBase64String(protectedData);
byte[] unprotectedBytes = dp.DangerousUnprotect(protectedBytes, ignoreRevocation, out requiresMigration, out wasRevoked);
return Encoding.UTF8.GetString(unprotectedBytes);
}
public static string PersistentProtect(
this IPersistedDataProtector dp,
string clearText)
{
byte[] clearBytes = Encoding.UTF8.GetBytes(clearText);
byte[] protectedBytes = dp.Protect(clearBytes);
string result = Convert.ToBase64String(protectedBytes);
return result;
}
}
I also created a helper class specifically for protecting certain properties on my SiteSettings object before it gets persisted to the db.
using cloudscribe.Core.Models;
using Microsoft.AspNet.DataProtection;
using Microsoft.Extensions.Logging;
using System;
namespace cloudscribe.Core.Web.Components
{
public class SiteDataProtector
{
public SiteDataProtector(
IDataProtectionProvider dataProtectionProvider,
ILogger<SiteDataProtector> logger)
{
rawProtector = dataProtectionProvider.CreateProtector("cloudscribe.Core.Models.SiteSettings");
log = logger;
}
private ILogger log;
private IDataProtector rawProtector = null;
private IPersistedDataProtector dataProtector
{
get { return rawProtector as IPersistedDataProtector; }
}
public void Protect(ISiteSettings site)
{
if (site == null) { throw new ArgumentNullException("you must pass in an implementation of ISiteSettings"); }
if (site.IsDataProtected) { return; }
if (dataProtector == null) { return; }
if (site.FacebookAppSecret.Length > 0)
{
try
{
site.FacebookAppSecret = dataProtector.PersistentProtect(site.FacebookAppSecret);
}
catch (System.Security.Cryptography.CryptographicException ex)
{
log.LogError("data protection error", ex);
}
}
// ....
site.IsDataProtected = true;
}
public void UnProtect(ISiteSettings site)
{
bool requiresMigration = false;
bool wasRevoked = false;
if (site == null) { throw new ArgumentNullException("you must pass in an implementation of ISiteSettings"); }
if (!site.IsDataProtected) { return; }
if (site.FacebookAppSecret.Length > 0)
{
try
{
site.FacebookAppSecret = dataProtector.PersistentUnprotect(site.FacebookAppSecret, out requiresMigration, out wasRevoked);
}
catch (System.Security.Cryptography.CryptographicException ex)
{
log.LogError("data protection error", ex);
}
catch (FormatException ex)
{
log.LogError("data protection error", ex);
}
}
site.IsDataProtected = false;
if (requiresMigration || wasRevoked)
{
log.LogWarning("DataProtection key wasRevoked or requires migration, save site settings for " + site.SiteName + " to protect with a new key");
}
}
}
}
If the app will need to migrate to other machines after data has been protected then you also want to take control of the key location, the default would put the keys on the OS keyring of the machine as I understand it so a lot like machinekey in the past where you would override it in web.config to be portable.
Of course protecting the keys is on you at this point. I have code like this in the startup of my project
//If you change the key persistence location, the system will no longer automatically encrypt keys
// at rest since it doesn’t know whether DPAPI is an appropriate encryption mechanism.
services.ConfigureDataProtection(configure =>
{
string pathToCryptoKeys = appBasePath + Path.DirectorySeparatorChar
+ "dp_keys" + Path.DirectorySeparatorChar;
// these keys are not encrypted at rest
// since we have specified a non default location
// that also makes the key portable so they will still work if we migrate to
// a new machine (will they work on different OS? I think so)
// this is a similar server migration issue as the old machinekey
// where we specified a machinekey in web.config so it would not change if we
// migrate to a new server
configure.PersistKeysToFileSystem(new DirectoryInfo(pathToCryptoKeys));
});
So my keys are stored in appRoot/dp_keys in this example.
If you want to do things manually;
Add a reference to System.Security.Cryptography.Algorithms
Then you can create instances of each algorithm type via the create method. For example;
var aes = System.Security.Cryptography.Aes.Create();

ASP.NET MVC Authentication Cookie Not Being Retrieved

I am having a hard time implementing "Remember Me" functionality in an MVC application with a custom principal. I have boiled it down to ASP.NET not retrieving the authentication cookie for me. I have included a snapshot below from Google Chrome.
Shows the results of Request.Cookies that is set within the controller action and placed in ViewData for the view to read. Notice that it is missing the .ASPXAUTH cookie
Shows the results from the Chrome developer tools. You can see that .ASPXAUTH is included here.
What may be the issue here? Why does ASP.NET not read this value from the cookie collection?
My application uses a custom IPrincipal. BusinessPrincipalBase is a CSLA object that ust implements IPrincipal. Here is the code for that:
[Serializable()]
public class MoralePrincipal : BusinessPrincipalBase
{
private User _user;
public User User
{
get
{
return _user;
}
}
private MoralePrincipal(IIdentity identity) : base(identity)
{
if (identity is User)
{
_user = (User)identity;
}
}
public override bool Equals(object obj)
{
MoralePrincipal principal = obj as MoralePrincipal;
if (principal != null)
{
if (principal.Identity is User && this.Identity is User)
{
return ((User)principal.Identity).Equals(((User)this.Identity));
}
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool Login(string username, string password)
{
User identity = User.Fetch(username, password);
if (identity == null || !identity.IsAuthenticated)
{
identity = (User)User.UnauthenicatedIdentity;
}
MoralePrincipal principal = new MoralePrincipal(identity);
Csla.ApplicationContext.User = principal;
Context.Current.User = identity;
return identity != null && identity.IsAuthenticated;
}
public static void Logout()
{
IIdentity identity = User.UnauthenicatedIdentity;
MoralePrincipal principal = new MoralePrincipal(identity);
ApplicationContext.User = principal;
Context.Current.User = identity as User;
}
public override bool IsInRole(string role)
{
if (Context.Current.User == null || Context.Current.Project == null)
{
return false;
}
string userRole = Context.Current.User.GetRole(Context.Current.Project.Id);
return string.Compare(role, userRole, true) == 0;
}
The application also uses a custom membership provider. Here is the code for that.
public class MoraleMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
bool result = MoralePrincipal.Login(username, password);
HttpContext.Current.Session["CslaPrincipal"] = ApplicationContext.User;
return result;
}
#region Non-Implemented Properties/Methods
public override string ApplicationName
{
get
{
return "Morale";
}
set
{
throw new NotImplementedException();
}
}
// Everything else just throws a NotImplementedException
#endregion
}
I do not think that any of this is related because the bottom line is that the Request.Cookies does not return the authentication cookie. Is it related to the size of the cookie? I heard there are issues to the size of the cookie.
UPDATE: It seems that the issue revolves around subdomains. This site was being hosted with a subdomain and the cookie domain was left blank. Does anyone have any pointers on how I can get the auth cookie to work with all domains (e.g. http://example.com, http://www.example.com, and http://sub.example.com)?
If you are trying to store the actual User object in the cookie itself, it is probably too big to store as a cookie. I am not too familiar with the MVC authentication stuff, but in web forms I generally do the following:
FormsAuthentication.RedirectFromLoginPage(user_unique_id_here, false);
The second parameter is for the persistency you are looking for.
From there I create a custom context (UserContext) that I populate via HttpModule that gives me access to all the user and role information.
Since I do not develop in MVC (yet) or CSLA, I'm not sure how much more help I can be. If I were you, I would also ditch the custom membership provider. You might as well just call MoralePrincipal.Login directly in your Authentication controller.
The rememberMe stuff should be set by the FormsAuthenticationService (in MVC2) or the FormsAuthentication static class in MVC1, if you're using the 'regular' AccountController's code. If you changed that code, did you remember to add in the (optional) boolean param indicating whether to use a persistent cookie or not?
It sounds to me like you're getting a session cookie, but not a persistent cookie.

Resources