Sometimes users get Invalid Token when clicking on their email confirmation link. I can't figure out why, it's purely random.
Here is the code that creates the user:
IdentityResult result = manager.Create(user, "Password134567");
if (result.Succeeded)
{
var provider = new DpapiDataProtectionProvider("WebApp2015");
UserManager<User> userManager = new UserManager<User>(new UserStore<User>());
userManager.UserTokenProvider = new DataProtectorTokenProvider<User>(provider.Create(user.Id));
manager.UserTokenProvider = new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));
var emailInfo = new Email();
string code = HttpUtility.UrlEncode(Context.GetOwinContext().GetUserManager<ApplicationUserManager>().GenerateEmailConfirmationToken(user.Id));
string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request);
if (email.IndexOf("#") != -1)
{
if (assignedId == 0)
{
lblError.Text = "There was an error adding this user";
return;
}
string emailcontent = emailInfo.GetActivationEmailContent(assignedId, callbackUrl, userRole);
string subject = emailInfo.Subject;
if (string.IsNullOrEmpty(subject))
{
subject = "Your Membership";
}
Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.SendEmail(user.Id, subject, emailcontent);
if (user.EmailConfirmed)
{
IdentityModels.IdentityHelper.SignIn(manager, user, isPersistent: false);
IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
}
else
{
ErrorMessage.ForeColor = Color.Green;
ErrorMessage.Text = "An email has been sent to the user, once they verify their email they are ready to login.";
}
}
else
{
ErrorMessage.ForeColor = System.Drawing.Color.Green;
ErrorMessage.Text = "User has been created.";
}
var ra = new RoleActions();
ra.AddUserToRoll(txtEmail.Text, txtEmail.Text, userRole);
}
else
{
ErrorMessage.Text = result.Errors.FirstOrDefault();
}
Here is the confirmation page that gives the 'invalid token' error
protected void Page_Load(object sender, EventArgs e)
{
var code = IdentityHelper.GetCodeFromRequest(Request);
var userId = IdentityHelper.GetUserIdFromRequest(Request);
if (code != null && userId != null)
{
var manager = Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
var confirmId = manager.FindById(userId);
if (confirmId != null)
{
var result = manager.ConfirmEmail(userId, HttpUtility.UrlDecode(code));
if (result.Succeeded)
{
return;
}
else
{
lblError.Text = result.Errors.FirstOrDefault();
txtNewPassword.TextMode= TextBoxMode.SingleLine;
txtNewPassword.Text = "Error contact support";
txtNewPassword2.TextMode= TextBoxMode.SingleLine;
txtNewPassword2.Text = result.Errors.FirstOrDefault();
txtNewPassword.Enabled = false;
txtNewPassword2.Enabled = false;
imageButton1.Enabled = false;
}
}
else
{
lblError.Text = "Account Does Not Exist";
imageButton1.Enabled = false;
}
}
}
Live Demo Project
I've created a pared-down demo project for you. It's hosted on GitHub here and is live on Azure here. It works as designed (see edits about Azure Websites) and uses a similar but not identical approach as you used.
It started with this tutorial, and then I removed the cruft that came with this NuGet demo code:
Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
For your purposes, my demo code is more relevant than the NuGet sample is, because it focuses just on token creation and validation. In particular, take a look at these two files:
Startup.Auth.cs.
We're instantiating the IDataProtectionProvider only once per application start.
public partial class Startup
{
public static IDataProtectionProvider DataProtectionProvider
{
get;
private set;
}
public void ConfigureAuth(IAppBuilder app)
{
DataProtectionProvider =
new DpapiDataProtectionProvider("WebApp2015");
// other code removed
}
}
AccountController.cs.
Then within the AccountController, we're using the static provider instead of creating a new one.
userManager.UserTokenProvider =
new DataProtectorTokenProvider<User>(
Startup.DataProtectionProvider.Create("UserToken"));
Just doing that might remove the bug you're seeing. Here are some questions for you to consider while troubleshooting further.
Are you using two different UserTokenProvider purposes?
The DataProtectorTokenProvider.Create(string[] purposes) method takes a purposes argument. Here is what MSDN has to say about that:
purposes. Additional entropy used to ensure protected data may only be unprotected for the correct purposes.
When you create the user code, you're using (at least) two different purposes:
user.Id
"ConfirmUser" and
the purpose of the ApplicationUserManager that you retrieve with GetOwinContext()....
Here's your code as a snippet.
userManager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create(user.Id));
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));
string code = Context
.GetOwinContext()
.GetUserManager<ApplicationUserManager ()
.GenerateEmailConfirmationToken(user.Id)
When you validate the code, you might be using the wrong purpose. Where do you assign the UserTokenProvider for the ApplicationUserManager that you use to confirm the email? It's purposes argument must be the same!
var manager = Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
var result = manager.ConfirmEmail(userId, HttpUtility.UrlDecode(code));
There is a strong chance that the token is invalid because you're sometimes using a different UserTokenProvider purpose for creation than you're using for validation.
Why would this be sometimes? Do a thorough search of your code to find all the places that assign to UserTokenProvider. Maybe you override it somewhere unexpected (such as in a property or in the IdentityConfig.cs file) so that it seems random.
Has the TokenLifespan expired?
You've mentioned that the Invalid Token message occurs randomly. It might be that the token has expired. This tutorial notes that the default lifespan is one day. You can change it like this:
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>
(dataProtectionProvider.Create("WebApp2015"))
{
TokenLifespan = TimeSpan.FromHours(3000)
};
Why three UserManager instances?
Here are some comments on the code that creates the confirmation token. It seems that you're using a three separate UserManager instances including a derived ApplicationUserManager type. What's that about?
What is the type of manager here?
Why create a userManager instead of using the existing manager?
Why use manager.UserTokenProvider not userManager.UserTokenProvider?
Why are you getting a third UserManager instance from the Context?
Note that I have removed a lot of code to focus on just your token creation.
// 1.
IdentityResult result = manager.Create(user, "Password134567");
if (result.Succeeded)
{
var provider = new DpapiDataProtectionProvider("WebApp2015");
// 2.
UserManager<User> userManager =
new UserManager<User>(new UserStore<User>());
userManager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create(user.Id));
// 3.
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));
// 4.
string raw = Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.GenerateEmailConfirmationToken(user.Id)
// remaining code removed
}
I wonder whether we could simplify the above to use just one UserManager instance as follows.
// 1.
IdentityResult result = manager.Create(user, "Password134567");
if (result.Succeeded)
{
var provider = new DpapiDataProtectionProvider("WebApp2015");
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create(user.Id));
// 3.
var provider = provider.Create("ConfirmUser");
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider);
// 4.
string raw = manager.GenerateEmailConfirmationToken(user.Id);
// remaining code removed
}
If you use this approach, be sure to use the same "ConfirmUser" purposes argument during confirmation of the email.
What's inside IdentityHelper?
Since the error is happening randomly, it occurs to me that the IdentityHelper methods might be doing something funky to the code that mucks up things. What's inside each of these methods?
IdentityHelper.GetUserConfirmationRedirectUrl()
IdentityHelper.RedirectToReturnUrl()
IdentityHelper.GetCodeFromRequest()
IdentityHelper.GetUserIdFromRequest()
I might write some tests to ensure that the raw code that your process creates always matches the raw code that your process retrieves from the Request. In pseudo-code:
var code01 = CreateCode();
var code02 = UrlEncode(code01);
var request = CreateTheRequest(code02);
var response = GetTheResponse();
var code03 = GetTheCode(response);
var code04 = UrlDecode(code03);
Assert.AreEquals(code01, code04);
Run the above 10,000 times to ensure that no problems exist.
Conclusion
It's my strong suspicion that the problem lies in using one purposes argument during token creation and another during confirmation. Use one purpose only and you might be fine.
Making this work on Azure Websites
Use SqlCompact instead of localdb.
Use app.GetDataProtectionProvider() not DpapiDataProtectionProvider because Dpapi does not work with web farms.
Is the site hosted on multiple web servers?
If so, you cannot use DPAPI here. It is machine-specific.
You'll need to use another data protection provider.
There might be some url-invalid characters in the code (token). Thus we need to use HttpUtility.UrlEncode(token) and HttpUtility.UrlDecode(token) when it appears in any url.
See something details here:
Identity password reset token is invalid
Whilst I think Shaun has provided great feedback to resolve such issues. There is one comment you made that makes me think it just might be an Alternate token issue. See also comments about multiple servers and Tokens.
... it's purely random
I dont think its random ;-). But what could make it work much of the time then for some users not.
A token was generated for a different page or APP, or is from the same APP and has just expired. Eg token valid for a short period. Is present in the browser. The token is from a similar app or from the same app on a different form/page.
The browser present the token since the token was generated for the Domain.
But when analyzed the token didnt match, or just expired.
Consider the lifecycle of the tokens.
Related
For context, this is FireStore using Unity SDK on C#
I've got 2 databases, one public that anyone can read and one private for authenticated users. They both work perfectly fine independently or together if user is authenticated before making a call to the publicly accessible database.
The issue is, if a user makes a call to public database and later on signs in, the calls to the private database come back with insufficient permissions exception.
I feel like original unauthenticated credentials persist somewhere and are being used instead of authenticated user, but could not find a way of disposing of them. Or I may have just missed something super obvious, but this has been plaguing me for far too long.
The following are the rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Public/{document}{
allow read: if resource != null;
allow write: if false;
}
match /User/{userId}{
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
And the following are the relevant call parts that in this order would create an issue I'm describing, this is heavily trimmed of value handling, but yields same result.
Call to public
CollectionReference dailyLevelsRef = db.Collection("Public");
DocumentReference docRef = dailyLevelsRef.Document(docID);
docRef.GetSnapshotAsync().ContinueWithOnMainThread(task =>
{
DocumentSnapshot snapshot = task.Result;
});
Authorization call
Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
Firebase.Auth.Credential credential =
Firebase.Auth.EmailAuthProvider.GetCredential("email#email.email", "password");
auth.SignInWithCredentialAsync(credential).ContinueWithOnMainThread(task => {
Firebase.Auth.FirebaseUser newUser = task.Result;
m_playerFireBaseID = newUser.UserId;
});
Call to Private
CollectionReference saveDataRef = db.Collection("User");
DocumentReference docRef = saveDataRef.Document(m_playerFireBaseID);
docRef.GetSnapshotAsync().ContinueWithOnMainThread(task =>
{
DocumentSnapshot snapshot = task.Result;
});
I'm operating an ASP.NET MVC application that leverages Microsoft OpenID Connect.
(manual : https://learn.microsoft.com/ko-kr/azure/active-directory/develop/tutorial-v2-asp-webapp)
We began our service in October 2019, and had no problems with it until now. After the last successful log in, we are facing authorization failure in the redirected part and cannot verify the Claim (User.Identity) information, hence resulting in an infinite loop issue.
I hope I can get assistance with this problem.
After logging in successfully, I store the authentication information so that I can share the token value in the RESTful API which is configured separately.
In the earlier version, the token value was stored in the Token table and was stored in the session.
But when I started to used the session from the second attempt, the problem started to occur so I started not to use the session.
At this point, I tried several login attempts and saw that it worked well without a problem.
Initially, the application was properly working, but as time passed by, I can no longer retrieve the authentication information and an infinite loop occurs once again.
Code:
public void SignIn()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties{ RedirectUri = "/Auth/SignedIn" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
public void SignOut()
{
this.RemoveSessionToken();
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType,
CookieAuthenticationDefaults.AuthenticationType);
}
public async Task<ActionResult> SignedIn()
{
var IsLoggedIn = Request.IsAuthenticated;
var claims = (System.Security.Claims.ClaimsIdentity) ClaimsPrincipal.Current.Identity;
if (IsLoggedIn)
{
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
//string sessionToken = Session.SessionID;
string ipAddress = Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (String.IsNullOrEmpty(ipAddress))
ipAddress = Request.ServerVariables["REMOTE_ADDR"];
var dbContext = new GetOfficeEduUsersEntities();
var token = new T_APIToken();
string office365Id = userClaims?.FindFirst("preferred_username")?.Value;
token.TokenID = office365Id;
token.IssuerID = office365Id;
token.IssueDT = DateTime.Now;
token.ExpireDT = DateTime.Now.AddMinutes(180);
dbContext.T_APIToken.Add(token);
dbContext.SaveChanges();
return RedirectToAction("Index", "Home");
}
else
{
//Error
return RedirectToAction("SignedIn");
}
}
Using the backend of my app, I am attempting to capture information from Microsoft Graph for a user that has been authenticated and then add that user to a database. The authentication appears to be working correctly, but the user is never added to the database. I am really stuck on this. I've studied the online documentation extensively, but have been unable to find a solution. If I could just tell if the user properties were getting populated, I could figure out what's going on, but I've been unable to do that since the code runs on the server. (I've attempted to remote debug, but have been unable to successfully set a breakpoint.) Can anyone tell me what I'm doing wrong in the code below?
class MicrosoftAccountInfo
{
public string id { get; set; }
public string displayName { get; set; }
public string mail { get; set; }
}
[MobileAppController]
public class MicrosoftAccountController : ApiController
{
MicrosoftAccountCredentials credentials;
string msRequestUrl;
MyAppContext context;
EntityDomainManager<User> domainManager;
// GET api/<controller>
public async Task<User> Get()
{
if (credentials == null)
{
credentials = await this.User.GetAppServiceIdentityAsync<MicrosoftAccountCredentials>(this.Request);
}
msRequestUrl = "https://graph.microsoft.com/v1.0/me/?$select=id,displayName,mail";
var client = new System.Net.Http.HttpClient();
var headerValue = "Bearer" + credentials.AccessToken;
client.DefaultRequestHeaders.Add("Authorization", headerValue);
var resp = await client.GetAsync(msRequestUrl);
resp.EnsureSuccessStatusCode();
var msInfo = await resp.Content.ReadAsStringAsync();
MicrosoftAccountInfo info = JsonConvert.DeserializeObject<MicrosoftAccountInfo>(msInfo);
context = new MyAppContext();
domainManager = new EntityDomainManager<User>(context, Request);
var user = context.Users.FirstOrDefault(u => u.Email == info.mail);
if (user == null)
{
user = new DataObjects.User { Email = info.mail, UserName = info.displayName, ProviderId = info.id };
await domainManager.InsertAsync(user);
}
else if (string.IsNullOrEmpty(user.ProviderId))
{
user.UserName = info.displayName;
user.ProviderId = info.id;
await context.SaveChangesAsync();
}
return user;
}
}
As to why this is failing, it is difficult to determine without an actual error message. There are simply to many variables/potential failure points involved to say for sure.
That said, you can reduce the number of potential failure points by using the Microsoft Graph .NET Client Library. There is also a NuGet package available: Install-Package Microsoft.Graph.
This library will handle composing the Microsoft Graph and deserializing the response into an object. Along with removing a risk factor, it will greatly simplify your code:
Microsoft.Graph.GraphServiceClient graphClient =
new Microsoft.Graph.GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", "{your-access-token}");
return Task.FromResult(0);
}));
Microsoft.Graph.User user = await graphClient.Me.Request().GetAsync();
I would also suggest implementing a monitoring solution that can trap exceptions on the server. This will help with debugging. If you're running on Azure, I strongly recommend using Application Insights. Aside from being free to get started, it is effectively a "click once, get monitoring" solution. It will handle wiring up the server and provide reporting for any exceptions it runs into.
Note that you can also use App Insights with your own servers or apps hosted on other services (i.e. AWS, RackSpace), there may however be some manual configuration required.
We just launched our ASP.net MVC (using Identity 2.0) web app 6 days ago and have had over 5000 people register for accounts, great!
The issue is 3-5% of those users who click the confirmation email link (and same seems to go for reset password) get shown the error screen presumably because they have an invalid token.
I read on StackOverflow here that you need to encode the url to avoid special characters throwing it off and then decode it right before you validate it. I did that to no effect though, some users were still getting errors on their validation token.
I also read that having different MachineKey's could be a reason tokens aren't processed as being valid. Everything is hosted on Azure so I presumed (and saw on SO) it was or should taken care of
So with 30-50 people emailing us for the past 6 days now about issues, I got desperate while I tried to come up with a solution and set my confirmEmail action to be the following:
[AllowAnonymous]
public ActionResult ConfirmEmail(string userId = null, string code = null)
{
if (userId == null || code == null)
{
return View("Error");
}
else
{
var emailCode = UserManager.GenerateEmailConfirmationToken(userId);
var result = UserManager.ConfirmEmail(userId, emailCode);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
}
I thought to myself there is no way in heck this could fail, it literally just generates a token and then immediately uses it - yet somehow it still fails (by fails I mean the user sees the error page)
My best guess as to a possible solution so far is this answer from SO (Asp.NET - Identity 2 - Invalid Token Error, halfway down)
Every time when a UserManager is created (or new-ed), a new
dataProtectionProvider is generated as well. So when a user receives
the email and clicks the link the AccountController is already not the
old one, neither are the _userManager and it's token provider. So the
new token provider will fail because it has no that token in it's
memory. Thus we need to use a single instance for the token provider.
But this is no longer necessary with all the OWIN stuff, right?
Is that really the issue still? If so, what the heck ASP.net Identity team? Why?
Some of the things I have changed:
The default Register Action recommends sending confirmation emails the following way:
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
Where I have the following in my Register Action
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your XXXXXXX account");
And then I specify the following in the SendEmailConfirmationTokenAsync
private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = userID, code = code }, protocol: Request.Url.Scheme);
// construct nice looking email body
await UserManager.SendEmailAsync(userID, subject, htmlBody);
return callbackUrl;
}
To me, both sections are equivalent, is this not the case?
And then the only other thing I can think of is how I added my db class, but that shouldn't affect the UserManager should it?
The top part of my account controller looks like the following (which is how the provided example from MS came + adding my database):
private readonly SiteClasses db = new SiteClasses();
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
{
UserManager = userManager;
SignInManager = signInManager;
}
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
...
We are using the recommended email provider sendgrid and I have personally never been able to replicate this issue (after creating ~60 test accounts manually) and most people seem to get along fine.
Part of this could be errors resulting from the users themselves, but this seems to be happening for a bunch of non tech-savvy people who just click on the link and expect it to work like a normal confirmation email should. Most users are coming to us from their iPhone where I would presume they are just using Apple's default mail client but not positive. I don't know of any spam filter or email settings that would remove the query string values from links.
I'm getting somewhat desperate for answers as I am trying to launch a great new startup but am getting hung up by ASP.net Identity technicalities or bugs or whatever is going on.
Advice on where to look or how to set up things would be greatly appreciated
I send the email as part of the URL currently.
We have a customer with a forwarding email account at tufts.edu going to gmail.com and it must be rewriting her email address that is part of the URL - which is HORRENDOUS but I can't see what else it is possibly doing.
This is one more thing to be aware of if I can confirm this.
I ended up confirming the email manually in the AspNetUsers table instead of creating a new token on the fly and trying to use UserManager.ConfirmEmail.
[HandleError(ExceptionType = typeof(HttpException), View = "ConfirmEmail")]
public ActionResult ConfirmEmail(string userId = null, string code = null)
{
if (userId == null || code == null)
{
return View("Error");
}
var result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
return View("ConfirmEmail");
}
else
{
var user = DBContext.Users.Find(userId);
user.EmailConfirmed = true;
DBContext.SaveChanges();
throw new HttpException(result.Errors.FirstOrDefault());
}
}
I also used [HandleError(ExceptionType = typeof(HttpException), View = "ConfirmEmail")] to still log the error but direct the use to the ConfirmEmail page still.
Not a great solution but I haven't been able to find anything to fix this issue.
in ASP MVC5 RC I didn't get the role system to work.
My database has all needs tables an role exist but proofing if user is in role always return false (no SQL exception or something)!?
Did I need to activate role system for IPrincipal somewhere?
Test code:
AccountController accCont = new AccountController();
// check role exist : result = true
var roleExist = await accCont.IdentityManager.Roles.RoleExistsAsync("61c84919-72e2-4114-9520-83a3e5f09de1");
// try find role by name : result = role object
var role = await accCont.IdentityManager.Roles.FindRoleByNameAsync("ProjectAdministrator");
// check with AccountController instance : result = true
var exist = await accCont.IdentityManager.Roles.IsUserInRoleAsync(User.Identity.GetUserId(), role.Id);
// check if current user is in role : result (both) = false????
var inRole = User.IsInRole(role.Id);
var inRole2 = User.IsInRole(role.Name);
I also try to build an custom extenuation like the IIdentity.GetUserId() extension method from Microsoft.AspNet.Identity.Owin Namespace.
namespace Microsoft.AspNet.Identity
{
public static class IdentityExtensions
{
public static string IsUserInRole(this IIdentity identity)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
ClaimsIdentity identity2 = identity as ClaimsIdentity;
if (identity2 != null)
{
var result = identity2.FindFirstValue(IdentityConfig.Settings.GetAuthenticationOptions().RoleClaimType);
return null; // later result
}
return null;
}
}
}
But the result for claim Type RoleClaimType is always null :(
I'm really stuck with this.
Thank you for your help! Steffen
I'm trying to understand how to use roles in MVC 5 myself, which is what brought me here. I can't answer your question, but check out this link. The downloaded solution works right out of the box and I've already been able to cut-and-paste some of the code and get it working in my own app. Now I'm trying to fully understand what it's doing.
http://www.typecastexception.com/post/2013/11/11/Extending-Identity-Accounts-and-Implementing-Role-Based-Authentication-in-ASPNET-MVC-5.aspx
It may not answer your question but at least it's a fully working solution that actually does work as described without a lot of hassle, so it's a good starting point.
User.IsInRole is basically looking at the claims for the currently signed in user. What does your sign in logic look like? That is what is responsible for minting the cookie that turns into the User identity. That needs to have the Role claim set properly for the IsInRole method to work correctly.