In the final part of our series on hand-rolling an authentication system, we tackle the registration form- how users become a part of the system.
To do this, we copy a lot of what worked in the login system, but change some key things and put in some more robust validation.
Code
The register template is similar to the login template:
<h1>Register</h1>
<div class="field">
<label>User Name</label>
{{input value=userName}}
{{display-errors errors=errors.userName showErrors=showErrors}}
</div>
<div class="field">
<label>Password</label>
{{input value=password type="password"}}
{{display-errors errors=errors.password showErrors=showErrors}}
</div>
<div class="btn btn-primary" {{action 'register'}}>Register</div>
The register controller is also very similar, but the validation logic has been tightened up, along with the error-handling logic:
import Ember from 'ember';
import EmberValidations from 'ember-validations';
export default Ember.Controller.extend(EmberValidations, {
showErrors: false,
session: Ember.inject.service(),
validations: {
userName: {
presence: true
},
password: {
presence: true,
length: {minimum: 6}
}
},
actions: {
register(){
let {userName, password} = this.getProperties('userName', 'password');
this.validate().then(()=>{
return this.get("session").register(userName, password)
}).then(()=>{
this.get('flashMessages').success('You have signed up successfully')
this.transitionToPreviousRoute()
}).catch((reason)=>{
this.set("showErrors", true)
if(typeof(reason) === 'string'){
this.get('flashMessages').danger(reason, {sticky: true})
}
})
}
},
transitionToPreviousRoute(){
var previousTransition = this.get('previousTransition');
if (previousTransition) {
this.set('previousTransition', null);
previousTransition.retry();
} else {
// Default back to homepage
this.transitionToRoute('index');
}
}
})
The register
method on the session service is also similar, just to a different endpoint:
register(userName, password){
return new Promise((resolve, reject)=>{
Ember.$.ajax({
method: "POST",
url: '/users',
data: {
email: userName,
password: password
}
}).then((data)=>{
var token = data['authentication_token']
var user_id = data['user_id']
Cookies.set('userId', user_id)
Cookies.set('authenticationToken', token)
this.initializeFromCookie()
resolve()
}, (reason)=>{
reject(`Server error: ${Ember.get(reason, 'responseJSON.error')}`)
})
})
},
On the server side, I edited the POST /users endpoint:
class UsersController < ApplicationController
before_action :authorize_user, except: [:create]
def resource_class_name
'user'
end
def create
email = params[:email]
password = params[:password]
if user = User.find_by(email: email)
render status: 400, json: {error: "A user with that email address already exists"}
else
user = User.create(email: email, password: password, password_confirmation: password)
user.save
render json: {authentication_token: user.authentication_token, user_id: user.id}
end
end
end
Transcript
Hello and welcome back. This is the last in our series about hand rolling your own authentication system. So previously we had authenticated routes as well as ways to login, but we don’t have a way to register a user yet. We’re going to be doing that today, and we’re going to be combining the stuff we’ve learned in the previous episodes to create something that is similar but has its own special requirements.
So first we’re going to create the register
route,
and then we’ll add a link-to
register if you’re not signed in.
Then we’ll need a template for our new route. We’ll go ahead and copy the login template since it’s going to be very similar, and then just change a few things so that it makes sense.
This form as well as several other things we do this episode do repeat themselves, and so are good candidates for a refactor. However, we won’t be tackling that this episode.
So this is what that gets us. Now we need to make the Register button do something.
The login controller already has functionality very similar to what we need, so we’ll copy it and change a few things like we did before, but here we’re going to have to change a few more things.
The first is that we want our validations to explicitly run before we call anything on the session. We could’ve done this on Login, and we probably should’ve, but it’s even more important now.
So the validations are a promise and then our session register is also a promise, so we’ll go ahead and stack those promises.
So we have several different promises happening here, stacked on top of each other, and they’re caught by the same catch block.
And this is generally good, except now we have the reason
coming back, the error coming back, in different formats sometimes. So we’ll need to take care of that.
We’ll do this by checking the type
and if it’s a string
, something that’ll look nice to the user, will show a flash message. Otherwise we just take the error and assume that it’s the type that is going to show up in the template already.
Now obviously this won’t work for all situations. You need to customize this so it’s specific to what you’re expecting to be returned.
So now we have the register
action in the register
controller. Let’s build the register
method in the session service.
Once again, we’re going to copy the login and then expand on it.
The changes here are fairly easy. So we name it register
instead of login
, and then instead of posting to the sessions
route, we post to the users
route. We could also post to a register
route if we wanted to, if we made our server like that.
Finally, we make changes in our error handling.
So previously, there was always the same error,
but now we might get back multiple errors, so we have to take that into account. So we’ll get the response
and then we’ll get the responseJSON
and the error
from that using Ember.get
.
Now this screencast is mostly about the JavaScript, but let’s take a quick peek at our server method. So this is the create
users method, and we’ll basically be getting the email and password. If there’s already a user with that email, then we’ll send back an error. Otherwise, we’ll create the user, save it, and then send back their authentication_token
and their user_id
.
You also note that although we usually do the authorize_user
action before everything, we create an exception for the create
method.
So now let’s see this in action. So we type a user name, and of course before it even sends off to the server, it does the validations.
So let’s get rid of those validations, and good, we’ve registered.
Now we can try to register that same one again and it’ll give us the server error.
So this is the end of our series about how to rule your own authentication method in Ember. This understanding that you’ve gained should help you in the next week or two when we explore Ember Simple Auth and Torii, and we’ll see how they can make things we’ve done easier and how they can do things that we hadn’t yet created. I’ll see you then.