Meteor comes with a great way to get started with authentication right out of the box, using the Accounts-UI package. Here I've got a login button that's telling me to configure the GitHub login. And when I click on it, it will ask me for the details required to authenticate with GitHub using single sign-on OAuth authentication.
This is great for trying things out, but as soon as you start building a production application, you're going to want to configure these details in the application itself. Also there's a good chance that you're going to want to customize the look and feel of the login button substantially in your own application.
In the rest of this tutorial, I'm going to show you how you can completely customize Meteor authentication, using custom configuration with GitHub OAuth as the provider. We'll look at the onLogin and onCreate user callbacks, and we'll look at how to publish custom properties of your user.
To start off, let's add the packages that we'll be using in this tutorial. First add the accounts-GitHub package, which will let us communicate with the GitHub OAuth service. Then I'll add the TWBS:bootstrap package that will give us the Bootstrap CSS library for styling. The HTTP package will let us communicate with the GitHub API to grab additional properties for the user. And finally, the Service Configuration package will let us customize our OAuth configuration.
In our HTML file, instead of using the login buttons template that comes with Accounts UI, we're going to build up our own login button. I've pasted in some HTML for the new nav menu using Bootstrap classes like nav bar and container fluid. And then inside of the unordered list element, I'm rendering out another template called login menu. And that's defined here, we just need to fill it out.
Inside the login menu, we're going to have to handle a couple of different states. Let me walk you through them. The first one says whether or not the user is logged in if we have a current user object. This current user helper is provided by the Meteor framework itself. If the user is not logged in, we have to handle a couple of different scenarios.
One is, are we in the process of logging in? This is another helper that's provided by Meteor. If we're in the process of logging in, we want to show something like a logging in text.
Let's start with the case where the user is logged in. So I've created a list item here with a class, a bootstrap class, of dropdown. And it has an anchor tag that will show the current users the login. So this'll be the login name that we get from GitHub. And then a little carrot icon that shows that this is a drop down menu.
Then inside the drop down menu, we'll provide a log out option. And notice that I have this data attribute on the anchor tag called data-action equals, and then the name of the action. I find this a useful pattern that we can use to find this element in our event handlers. So the action for this anchor will be to log out.
Next, if we're not logged in yet, we'll check whether or not we're in the process of logging in. And if we are, just go ahead and show a logging in text, and that will display temporarily while the login process is happening asynchronously.
Finally, if we're not logged in yet, and we're not in the process of logging in, that must mean that the user hasn't clicked the Login button yet. So in that case, as long as we're ready to go with our configuration, we'll show the user a Login with GitHub link.
Now we need to configure the OAuth application inside of GitHub itself. Click the Main menu and then the Settings option. Then look for the Applications menu on the left. Click on Developer Applications, and finally Register New Application. Fill in the application details and take special note of this authorization callback URL.
Notice, since this is a development application, it's localhost:port3000, and the path is this _oauth/github, or the name of the service that you're using for the OAuth authentication. Finally, when you're done, just click the Register Application button.
And we'll use this special upsert method that will insert a record if it doesn't exist yet. Otherwise it will just update the existing record with the new data. So what we're going to look for is a service named GitHub. And we're going to set a couple of properties on this record. The first will be the client ID, and then the secret.
And then a login style that we'd like to use. I'm going to use the pop up option. Copy and paste the client ID and secret that you got from GitHub in the previous step. Next, we'll add the event handlers required to wire the anchor tags up to our login events.
So these events are going to be on the login menu template itself, and I'll call the events method and pass an events object. The first event will be a click event on the action equal to log in. Notice that I'm using this data action attribute here that we saw in the HTML. It makes it really useful to associate that anchor tag with this particular event.
Now I'll use enhanced literal syntax from ES6, so we don't need the function keyword. And what I like to do in most anchor tag event handlers is prevent the default, just in case we accidentally submit the form or go somewhere else. So this will prevent the default browser behavior.
And then finally, we can call the login with GitHub method. It can be login with any one of the services that you're using, but since we're using GitHub, we'll say login with GitHub. And then you can provide it an object of options for the service. In this case, we're going to request some permissions from GitHub, and we're going to request that we see the-- be able to see the user's email.
Now let's add the event handler for log out. So that data action will equal log out. And we'll use the same ES6 syntax here, omitting the function keyword, and I'll prevent the default behavior for the link. And we'll simply call the log out method of Meteor.
Finally, remember the is login services configured helper? We're going to define that now on this template. So I'll call the helpers method and provide a helpers object. And we'll name this is login services configured. And again, we'll use ES6 syntax here, so we can emit the function keyword.
And I'm going to return the result of calling the login services configured method that's provided to us by Meteor. Now this is a reactive method that will return true, if the log in services are configured and we have that data from the server, and it'll return false if we don't yet have that data from the server. So this function will re-run if this value changes reactively.
If you save that file and head over to the browser, you should now see the bootstrap navigation menu at the top with our login with GitHub anchor over on the upper right. And we shouldn't see any errors in the console. And I should be able to click this Login with GitHub icon, and that should start the OAuth process.
GitHub should ask you whether you want to authorize this application, and when you click OK, the pop up will be closed, and you'll be returned to our own app. Notice the user interface is reactively updated to reflect the fact that we're now logged in. And if you click the carrot arrow, you'll see the log out anchor.
One thing that seems to be missing here is the user's login name is not showing up in the user interface. When we go back to the server, I'll show you how to fix that. But let me show you one more thing before we leave the browser.
Open up the debugger and click on the Resources tab, and then the Local Storage tab. When you open up Local Storage, you should see the domain that we're currently on, which is localhost3000. And inside if that, you'll see a couple of keys that were created by Meteor.
They're related to login. You can see login token expires and the user ID. So Meteor is storing the login information in local storage. If needed, you can even delete these. And notice that the user interface will update automatically and change us back to the login with GitHub link.
Let's log into Mongo and see the collections that were created for us, and take a look at their respective documents. If you type show collections, you can see there's a couple of accounts-related collections that have been created automatically. Let's take a look at the meteor accounts login service configuration.
And we'll find the first record. This is where Meteor is storing the custom OAuth configuration that we provided earlier, including the client ID and secret from GitHub. Next let's take a look at the user document that was created for us. First notice the services property, and we have a GitHub sub-property of that, that has all the information related to the GitHub authentication, including an access token that we can use to authenticate this user to GitHub and perform API requests on their behalf.
Then there's this profile property that has the name of the user but nothing else in there. So we're going to collect some additional information. For example, I'd like to grab the login and the email address for this user. So let's customize the login behavior on the server. There's two hooks in accounts that we can use to customize what happens when a user gets created or when they log in.
The first is called onCreate User, and we can pass that a function that will take options and a user as parameters. And I'm using the fat arrow syntax here to make this look a little bit nicer. And the second is called onLogin. And that will take a login info parameter, which we'll look at in a second.
The onCreate user hook will get called any time a new user is created, if that user doesn't already exist in our database. And the onLogin hook will get called every single time a user logs, in even when the login resumes. In other words, when they don't login explicitly, but the login happens automatically when they go back to the application.
The onCreate user callback needs to just return the final user object, and that's how Meteor will know what to put into the user's collection. So what we want to do with this user is assign a new profile object. That's going to come from a custom method we'll create in a second, called getUserInfo.
getUserInfo is going to make an API request to GitHub. And we're going to provide it an access token so that it has access to do that. So we'll go into the user object, the services object, GitHub, and then the access token. Then I'm going to set the login equal to the login that comes back, and the email equal to the email that comes back. And then just return the user directly.
The onLogin hook is going to provide this login info object that's going to tell you some things about the login process. If you want to learn more about it, you can console log it. In the meantime, we're just going to use one of the properties of it, called user. So I can grab the current user by saying, logininfo.user. And I'll assign that to the user variable.
And we'll grab the access token off of that user, similar to what we did above, by using the services GitHub, access token property. Then I'll grab the user's info from GitHub by calling this method, which we'll create in a second, and providing this access token. And finally in this method, we don't return the user. We actually need to make an update to the database.
So I'm going to say Meteor users update, and we're going to update the user with this user's ID. And then we'll set some properties of the user. I've pasted in the code to save some time, but I'm going to assign the user info object to the profile. And then notice the login and email I'm going to sign is root properties of the user. And I'll grab the login from the user info and the email from the user info as well.
So the last thing we need to do is to define this getUserInfo function. They getUserInfo function is going to take a GitHub access token as input, and then use Meteor's HTTP library to make a GET request to the API of GitHub. Be careful to provide the user agent header. That's a requirement that GitHub has added a few months ago. So we'll just say that the user agent is Meteor. If you don't do this, the API call will fail.
And as a parameter, we'll pass up the access token. Now what we'll get back, because we're going to the user API here, is some information related to the user. And we can grab that in the data property of the result. Then we'll use _pick method to grab the login and email properties of that data.
Back in the browser, we can click the Login with GitHub link to start this process and grab those additional properties from GitHub. But it looks like we're still not printing out the user login to the user interface. Let me explain why.
If you come down to the console and type Meteor user, notice the properties that are available on the object. It has services and the profile information, but our login and email are not on the root object, they're only in the profile. And that's because by default, Meteor only publishes out the profile and services property of the user, and of course the ID.
So in order to get the custom properties published out to the client, we need to write our own publish and subscribe methods. First, make sure that the auto publish package has been removed, so that we're not automatically publishing user information. That will make things quite confusing.
Next, in your server code, create a publish function for the user. And that publish function is going to return a cursor for the current user, which we're going to get using the this keyword, and then the user ID property. That will be set to the user for this particular publish function. And then we're going to tell the cursor which fields, explicitly, we want to publish.
So we'll send down the user ID, the profile, the login, and also the email. Finally, jump up to the client area and subscribe to our new publish function, so we get this user information in the browser.
OK, awesome. Now the user's login is showing up in the user interface as we expect. And if we come down into the console and type Meteor and call the user method of that, notice that the email and login information, or properties, are on the user now, as expected. And if you need to double check, you can click over onto the Network tab, take a look at the web sockets, click in, and you can see the user data being sent down the wire.
Notice the DDP message here for changing the user record with this ID and these fields. I hope this tutorial gave you a much better sense of how the Meteor authentication system works, and more importantly, how we can customize it to meet our own needs.