On Habré more than once and not two articles were published about the need to allow the user to log in via
Google /
Twitter /
Facebook , etc. As a matter of fact, progressive humanity has long decided that it is yesterday to require users to invent logins and passwords. In this article I want to discuss the problems that arise and how to solve them.
Small preface
At the moment I am one of the developers in the team of enthusiasts who have decided to write a kind of information and reference portal with social network elements for the
Kohana php framework. Authentication is required to write a comment / feedback, participate in polls and ratings, etc. Just screwing the entrance through OpenID / OAuth is easy, interesting things appear when you start to think - what next?
Probably it is necessary to warn in advance that, in principle, it will be a question of theoretical research. As soon as we have the described mechanism ready, which will not be ashamed to show, we will show it.
')
Authentication
Database
We sketch out approximate database tables that will allow us to store the maximum possible amount of information that we can pull from the user.
users
Everything is clear, this is the main table for storing the user.
id
- user id
. Auto increment This identifier we plan to use to communicate with other site objects.username
- visible on the site user name (Display Name). Initially, we generate it based on OpenID or OAuth data, but be sure to be given the opportunity to change it.email
- email address. Initially trying to pull out of the external account used. This is not always possible, so the field is optional.
auth_data
This table is for working with external user accounts. For example, if user A has a Google account and another Twitter account, then we must provide two
auth_data for one
user account - so that nothing is lost!
id
- in general, everything is clear. Synthetic auto-incrementing key.service_name
is the name of the provider used to log in. It must consist of an authentication mechanism and the actual name of the service. For example, if we logged in via OpenID using our livejournal account, then we’ll get 'openid.livejournal', and 'oauth2.github' means our github account, available to us through OAuth v2.service_id
- user account identifier on the above service. Those. login. Paired with service_name
we get a unique index, there should be no more such users on the site.email
- soap again! Its user can not change, and we can use it for their dirty purposes, for example, to search for duplicate users or generate a human login.some_data
- here we some_data
everything else (in serialized form) received from the provider. What if it comes in handy? Of course, the field can be thrown out, it is for the hoarse and shrewd hosts;)
Connections
Naturally, accounts should be tied to a specific user, so the
user_id foreign key appears in the
auth_data table.
Strangely enough, we also add the
auth_id
foreign key to the
users table in order to bind the user to an account. What for? Thus, we want to highlight a certain
primary (
primary ) account. For example, in order to automatically transfer some data from it to a user profile, or to implement autologin. The decision may seem controversial, but we currently consider it reasonable and useful.
Let me in, fiends!
And now - the actual authentication mechanism. Step by step.
- They clicked on the “login through” button, entered your OpenID or simply clicked on the provider’s image. We confirmed / proved to the provider that you are you.
- After the provider has sent you back to the site, you need to check the availability of this user in the database. What if you have already logged in? For this we use the
service_id
+ service_name
combination mentioned above. If the account is found, then everything is simple - we retrieve the user by user_id
and forward. - But no, the account previously did not fall into the field of view of our site, so let's quickly register it before it changes its mind.
In auth_data , we record everything that the provider has given. Based on this data, we are trying to generate the username
user. We save the new user, we specify the used account as primary . Hurray, we have a new user!
Well, what is there to discuss something?
Uh, quickly described the routine, let's look at various cases that require additional efforts from the programmer.
Remember me!
Often it becomes difficult to remember under what account previously visited the site. And why, let the site remember, it is more necessary for him. Therefore, after a successful login, we throw a cookie with a token in order to allow the user to log in automatically next time.
It’s not safe, you say, and you’ll probably be right. Therefore, when generating a token, we also save various information about the client (IP, User-Agent, etc. - there is a choice). If the user explicitly leaves the site (via the “exit” button), we kill the cookie. Well, in case of autologin, we need confirmation if the user wants to change some critical data (change soap or login). For extreme cases, you can draw the checkbox "do not remember me."
Naturally, in the case of OAuth, in order to use the service API (say, send a tweet to your account), a working token is needed, so you must either not use “memorization” in general, or, again, at the right time, send it to the provider for confirmation.
Remember? Not? Remember!
Even if the user logged out and
emigrated to Albania for a long time did not appear, it is necessary to help him with authentication. Let's go back to the moment when the user is logged in. Above, I wrote about a special cookie. It turns out that her one is not enough - we throw the second. With a long duration. Inside we store the account identifier through which you entered.
Come back. And here this long-playing cookie will help us. Based on it, we prompt the user with something like “Well, you’ve logged in through Google, buddy!”. Naturally, if he chooses a different account, we will change the cookie to a new one. In the end, we are just a site, we do not mind.
Login from different accounts
Yeah, under a different account, you say! So we will duplicate users? No thanks. We need to fight this, and we will make every effort to that.
First of all, we look at the very long-lived cookie. She should tell us that not everything is clean, and potentially we are creating a second account. Therefore, we ask the user whether it is a separate account or an existing one. If it is separate, then after preliminary cleaning of the cookies and other information we create a new entry with zero mileage.
But if the user claims that it is still he, just for some reason he wants to enter in an unusual way for us (we have all the usual ones in
auth_data , we remember this), then we will have to do a check. Offhand, I see several options - to offer to log in under the original account (ie, under
primary - this is another reason for its indication), or send a confirmation code to the email (if it is indicated in the user profile). Formally, we can not require just the input under the
primary , it is enough any of the already confirmed - as you wish.
If the user could not confirm his rights, then we delete the newly created account - there is nothing for us to lie.
Look at the mail!
Indeed, because the email address can be safely used as a user ID. Therefore, even if the cookie was not found, but the provider returned a familiar
email
to us - we offer the same dialogue with the choice of the path (merging accounts or a new one). Oh, it's a pity that not all accounts give away soap ...
Flag in your hands!
Why wait for a user to confuse their accounts? Let him indicate that he still has it in his storeroom. In the profile (only under the
primary account?), We provide the opportunity to add more accounts that can be considered “our own”. And do not have to confuse the person with incomprehensible questions.
About Github
Did you know that
Github supports OAuth v2? And he is, he can. For our project, this service is special, you might say, darling. As soon as the user logs in via Github, we immediately begin an additional lick.
- I did not talk about this, but in the users table we also store the
developer_id
field. This is the key to communicate with the developers table (hello to you, Cap!), In it we store the developers that Github passed us through our API. The fact is that we collect the developed modules for Kohana (this is one of the main tasks of the project), so it is very useful to know that some of the visitors are the author of some modules. - It is never superfluous to collect additional information from the user. And if the user is a programmer, then this information becomes even more useful (khe-khe). After successful registration, we remind Giethabber that we have N modules of his authorship, and he can edit some data (compatibility, versions, description, etc.). No, not so. We can handle it ourselves. But we will still send notifications to the developer about new modules added for his authorship (ok, ok, we'll also make the option to turn off notifications).
What am I talking about?
Naturally, there will be many pitfalls. I would like to discuss in advance the described algorithms and possible jambs / omissions, both from the site visitor’s side (how convenient this scheme is, there are suggestions, or maybe you’re constantly straining something on such sites?) And from the developers ’point of view (which they implemented at home, where there may be shoals). Indeed, in the comments to articles on Habré, there is often much more useful information than in the article itself.