12

I'm developing a MVC4 Website with a SQL Server database. I want to register people with their email address.

However if email address contains the character i, WebSecurity.CreateUserAndAccount method throws an exception says :

The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.

I researched a lot and find a few posts about it but there is no solution.

http://forums.asp.net/t/1862233.aspx/1?How+can+i+use+email+address+for+username+in+MVC4+Membership

http://aspnetwebstack.codeplex.com/workitem/714

JoshDM
  • 4,939
  • 7
  • 43
  • 72
ergunysr
  • 301
  • 1
  • 10
  • 4
    just use your own .NET Membership Provider, [here](http://stackoverflow.com/a/5702000/28004) I tell how easy it is to create and use one... Then, you can add whatever info you want. – balexandre Dec 22 '12 at 17:53
  • @balexandre thanks for reply.. it seems there is no other way – ergunysr Dec 22 '12 at 18:37
  • 4
    Probably worth mentioning that this is a manifestation of the so-called [Turkish 'I' Problem](http://msdn.microsoft.com/en-us/library/system.string.toupperinvariant%28VS.85%29.aspx) illustrated in the sample code of the post. This [other post](http://stackoverflow.com/questions/773703/normalization-of-strings-with-string-toupperinvariant) explains why the existing provider recommendations are in contradiction with the recommended use of `ToUpperInvariant()` fors situations like this one. – David Tansey Dec 22 '12 at 18:52
  • 2
    @DavidTansey using `ToUpperInvariant()` solved the issue thanks :) – ergunysr Dec 22 '12 at 19:37
  • @ergunysr: love to know where you ended up placing the `ToUpperInvariant()` to get resolution. I'm betting you are using it on the input value of UserName before passing it to `CreateUserAndAccount()` – David Tansey Dec 22 '12 at 19:40
  • @DavidTansey yeah thats right i started to feel nooby right now:/ i'm glad if you let me know where to put it – ergunysr Dec 22 '12 at 22:34
  • So you do NOT have a resolution yet -- correct? IOW -- you spoke too soon a little bit earlier? Let me see if I can make my db to believe that it is Turkish... – David Tansey Dec 22 '12 at 22:45
  • actually im not getting any error now. As we know email addresses are using same charset all over the world which is not included special turkish characters so when i get user information i ll use this email address with `ToLowerInvariant()`. I assume it will be ok. Ofcourse i want to know correct way.. – ergunysr Dec 22 '12 at 22:48
  • My current CreateUserAndAccount method is `WebSecurity.CreateUserAndAccount(model.UserName.ToUpperInvariant(), model.Password, new { RoleId = 4, CourseId = model.CourseId, Name = model.Name, SurName = model.SurName, NationalNumber = model.NationalNumber, Address = model.Address }, false);` – ergunysr Dec 22 '12 at 23:13
  • Excellent -- that's where I thought you might have placed `ToUpperInVariant()`. – David Tansey Dec 23 '12 at 00:50
  • @ergunysr two more questions: are there any other side effects besides the username (which is same as email address in your case) being stored in all UPPERCASE rather than in mixed-case? By being converted `ToUpperInvariant()` and then stored does that column value also lose its Turkish-ness? Are there any additional places in the code where you are having to compensate with `ToUpperInvariant()` such as at login? – David Tansey Dec 23 '12 at 01:37
  • @DavidTansey no, i used `ToUpperInvariant()` only for UserName. And as far as i see there is no side effects. BTW I found a solution for my case. I changed my **UserName column's collation** from Turkish to `SQL_Latin1_General_CP1_CI_AS` --English(United States) collation-- now there is no need for anything else. It worked like a charm – ergunysr Dec 23 '12 at 11:03
  • Be careful - SQL_Latin1_General_CP1_CI_AS allows for a LOT of characters in it. All the ascii characters up to 255 - so that includes accented i's, /tab, etc. - all kinds of stuff that do not belong in email addresses. We've had a problem with this recently and we had to add a character check in the user trigger. I have solutions to this, but they won't fit in my comment. – Brian White Jan 14 '13 at 22:12
  • Run this to see all the characters that fit in that encoding: select number, cast('.' + char(number) + '.' as varchar(10)) collate SQL_Latin1_General_CP1_CI_AS thechar from master..spt_values where type='p' and number between 28 and 255 order by number – Brian White Jan 14 '13 at 22:13
  • 2
    @ergunysr Can you post your solution as the answer? You can answer your own question and it improves the quality of SO since people will see this question as having a solution. – Colin Young Jan 25 '13 at 13:48
  • So does the problem actually have to do with the email address? Or is it just one instance of the "Turkish i problem?" – John Saunders Feb 03 '13 at 23:09

2 Answers2

3

Your fundamental problem is that you are trying to input non-ASCII characters as an e-mail address.

The "ASCII character set" limits you to the US-English alphabet, which happens to be the latin alphabet.

I don't speak turkish, but as pointed out in http://aspnetwebstack.codeplex.com/workitem/714

In Turkish, 'i' is lowercase, while 'İ' is upper of "i".
In the latin alphabet, which you (and me) happen to not have IT-wise, 'i' is lowercase, while 'I' is uppercase of i.

So you have a character which is not a member of the EN-US ASCII character set.
Therefore, the collation setting prevents you from inputting non-ASCII characters into your database, hence the error.
And hence the comment that you shall NOT change the collation setting (which would allow for invalid mail addresses).

As pointed out, ToUpper (which should be ToUpperInvariant) is to blame, because it behind the scenes changes the 'i' to 'İ', which is not a valid ASCII character.

This is a common problem with the ToUpper method of a string/character.
For example the German alphabet contains the letter ß (Unicode U+00DF), also known as "double s", which has no corresponding uppercase character in the first place, therefore if you try to compare strings using toUpper, it will always fail, which is why you should always use ToLower() to compare strings - another Microsoft fail.

A similar thing happens here with ToUpper.

What you need to do in the first place is to ensure your users input ASCII characters.
This is not possible since they have a turkish keyboard and locale, and while the small i will look similar to the ASCII small i, it has a different numerical representation, and therefore a different uppercase character (and lowercase as well btw).

So what you need to do is to "latinize/romanize" your input string, prior to calling the membership method.

I had similar problems with ASCII-only capable software from a third party, which failed on umlauts and French accent characters, and used the below method to resolve this.
You might want to check if the Turkish äöü doesn't have a different numerical representation than the (Swiss) German äöü that I used here.

For risks and side-effects, read the package leaflets and ask your doctor or pharmacist.

    // string str = ApertureSucks.Latinize("(æøå âôû?aè");
    public static string Latinize(string stIn)
    {
        // Special treatment for German Umlauts
        stIn = stIn.Replace("ä", "ae");
        stIn = stIn.Replace("ö", "oe");
        stIn = stIn.Replace("ü", "ue");

        stIn = stIn.Replace("Ä", "Ae");
        stIn = stIn.Replace("Ö", "Oe");
        stIn = stIn.Replace("Ü", "Ue");
        // End special treatment for German Umlauts

        string stFormD = stIn.Normalize(System.Text.NormalizationForm.FormD);
        System.Text.StringBuilder sb = new System.Text.StringBuilder();

        for (int ich = 0; ich < stFormD.Length; ich++)
        {
            System.Globalization.UnicodeCategory uc = System.Globalization.CharUnicodeInfo.GetUnicodeCategory(stFormD[ich]);

            if (uc != System.Globalization.UnicodeCategory.NonSpacingMark)
            {
                sb.Append(stFormD[ich]);
            } // End if (uc != System.Globalization.UnicodeCategory.NonSpacingMark)

        } // Next ich


        //return (sb.ToString().Normalize(System.Text.NormalizationForm.FormC));
        return (sb.ToString().Normalize(System.Text.NormalizationForm.FormKC));
    } // End Function Latinize

And last but not least, I would not use the built-in ASP.NET membership provider, because it joins tables via username + applicationname to rolename, instead of using a unique id. This means you will not be able to change a user or group/role name without changing all the mapping tables. I think doing that this way is highly undesirable, and definitely stupid and careless, if not outright hazardous from Microsoft's part.
I would go as far as calling it impudent to release such garbage into the wild.

The below "thing" demonstrates the problem's utter stupidity and should not ever be used

CREATE TABLE [dbo].[UsersInRoles](
    [Username] [varchar](255) NOT NULL,
    [Rolename] [varchar](255) NOT NULL,
    [ApplicationName] [varchar](255) NOT NULL,
 CONSTRAINT [usersinroles_pkey] PRIMARY KEY CLUSTERED 
(
    [Username] ASC,
    [Rolename] ASC,
    [ApplicationName] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

for the following reasons:

  • Problem 1: string join will make user to role join very slow - very bad for performance
  • Problem 2: varchar - should be nvarchar, unless you live in an English-only universe
  • Problem 3: length of field username & rolename must conform to minimum specs of latest active directory, otherwise there will be problems syncing with activedirectory. Plus Active directory allows for unicode characters.
  • Problem 4: No foreign key reference to the user and roles table - ends up with data garbage sooner or later (usually sooner)
  • Problem 5: if now you change the username or the groupname, UserInRoles will not be updated and the users's group mapping will orphan - ends up with data garbage, left joins will bring empty columns, programs might crash because of an unhandled NullReferenceException.
  • Problem 6: because the foreign key reference is missing, it is possible to change a username/groupname
  • Problem 7: data garbage will sooner or later lead to muliple rows when reporting/displaying data
  • Problem 8: The primary key used here should instead be a unique constraint
  • Problem 9: There is ONE group name, but for example group "administrators" needs to be localized to many languages, which the membership provider doesn't support. Additionaly, the groupname belongs into a mapping table, because one group can have N names for N languages, and then it must be ensured that the groupname is unique within a specific language.
  • Problem 10: Not visible here, but the user table contains a field e-mail. This is an utter fail because one user can have n email addresses, and a smart membership provider should take this into account, instead of stupidly limiting the user to ONE email address.
  • Problem 11: Plus additionally, the mentioned email field is limited to 128 character, which is smaller than the maximum allowed number of characters allowed in an email address as per rfc. FAIL - sooner or later somebody will be unable to input his mail address - even if he has only one. What is the maximum length of a valid email address?

and I am sure there are a lot more problems with the MS-supplied membership-provider fiasco. For example using a fast hash algorithm (MD5), which is an antipattern for this case, because that allows for rainbow table attack, especially if the hash is not salted. If the MS membership provider demonstrates one thing, then this is how NOT to design a membership provider.

Community
  • 1
  • 1
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
0

Yes the solution is to use ToUpperInvariant() when you create the user and account WebSecurity.CreateUserAndAccount(model.UserName.ToUpperInvariant()); . This will solve the issue. Values will be stored into the table with uppercase letter(IIIIII@GMAIL.COM).