Building URL shortener using React, Apollo and GraphQL — Part V: User Authentication

Written by pjausovec | Published 2018/01/06
Tech Story Tags: javascript | react | graphql | graphcool | tutorial

TLDRvia the TL;DR App

Photo by Mabel Amber

Table of Contents

Happy New Year and welcome back! In this part which is last part of the series, we are going to implement user authentication (login and sign up) as well as modify our queries, mutations and subscriptions to support this. At the moment, anyone can go to the site and create as many short links as they want. What we want want to do is to require users to sign-up first, then they can login to the site and can create their own links there without seeing anyone else’s links.

If you’re interested in looking at the final version of the project, visit the GitHub repo here.

Login and Sign-up React components

Let’s start with a two simple components that we are going to use to sign-up and login to the site.

Login component

Create thesrc/components/Login.js file with the following contents (we will come back in a bit to implement the login function and wrap the component with necessary containers):

This will give us an awesome looking login component (seriously, I need to do some CSS and get better with design — recommendations more than welcome!):

Awesome login component

Similarly, let’s do the sign up component.

Sign-up component

Sign-up looks pretty much the same as login:

Hooking up the components

In order to render these two new components, we need to slightly modify the AppRouter to say that whenever someone requests /login or /signup path, we are going to render the corresponding components. After you’ve imported both Login and Signup components in AppRouter.js, add the following two routes below the Home component route:

<Route exact path="/login" component={Login} /><Route exact path="/signup" component={Signup} />

First up yarn (yarn start) and navigate to /login and /signup to see the components render.

Now that we have the components in place, let’s go to the backend and bring in the email/password auth.

Add auth support to Graphcool service

Graphcool has support for templates that allow you to pull in various functionality to your projects — there’s a whole GitHub repo of officially supported templates that include, for example, Twilio integration, Mailgun integration (send text messages and emails as part of a subscription, in a resolver function, etc.) and (good news for us) email/password authentication. Other interesting auth related templates are Facebook auth, Google auth, Github, etc.

We are going to use the email-password authentication template that consists of 3 functions that are described in more details below.

Signup

For signup we define a new mutation called signupUser that has this signature:

signupUser(email: String!, password: String!): SignupUserPayload

This is the mutation we are calling when user tries to sign up for an account. What happens as part of this mutation is defined by our function that we define (similarly as we did in previous post where we defined a function that handled a subscription). Here’s what the signup function does:

  1. Validates the email address
  2. Checks if the user with that email exists or not
  3. Hashes the password
  4. Creates a new user
  5. Generates a token we use to identify the user

In the end, it returns an ID of a newly created user and an a token we will store locally. In addition to the things above, this would be the place where we could add things such as sending a welcome email, or making user confirm their email address etc.

Authenticate

Authenticate is another new mutation we define and it’s very similar in signature to the signup mutation:

authenticateUser(email: String!, password: String!): AuthenticateUserPayload

This is the mutation that gets called when user is trying to log in to our website. As part of this mutation we are going to check if the user with the provided email exists, and if it does, we ensure the passwords match then finally generate token and return it.

Logged-in user

The last part of the template is a query that we can execute and it’s going to return us user ID if the request itself contains a valid token. The idea is that we are going to execute this query on components that require authentication and then before rendering the component we can check if we got any user data back or not — if we didn’t we can redirect the user to the login page, and if we did, we can continue and render the component.

Let’s add the template to our service by running this command from our graphcool/ subfolder:

graphcool add-template graphcool/templates/auth/email-password

Above command will pull down the .graphql and .ts files that represent the mutations and the functions we explained above. We still need to manually update the types.graphql and graphcool.yml files to include these in our service. If you open any of those two files, you will see that some commented out declarations were added to it — this is Graphcool CLI helping us to define the functions and/or models.

Go ahead and uncomment the functions in graphcool.yml as well as the User model in the types.graphql file.

Note: we will come back to the _types.graphql_ file shortly and add the relation between User and Link models, so don’t worry about that at the moment.

Let’s deploy the changes and make sure all is good — run graphcool deploy and after a couple of seconds you should get the list of resolver functions and types that were created.

Next, let’s implement sign up and login functionality and then as a last step we will hook up the relation between Links and Users.

Sign me up!

As explained above we need to call the signupUser mutation to sign the user up and store the token we get back from the mutation.

This is the mutation we are going to use in Signup.js component:

const SIGNUP_USER_MUTATION = gql`mutation SignupUser($email: String!, $password: String!) {signupUser(email: $email, password: $password) {idtoken}}`;

We also need to wrap the component with graphql :

export default graphql(SIGNUP_USER_MUTATION, { name: 'signupUserMutation' })(Signup,);

Finally, we need to implement the signup function. Here’s the full implementation:

There’s nothing to complicated in the code above — we are calling the mutation then taking the ID and token and storing it in the local storage. Finally, we redirect the user to home page.

Note: you probably noticed the lack of proper error handling —writing errors blindly to the console is not good— make sure you always parse the errors and show the sanitized error message , instead of full errors that could include a call stack and possibly information that shouldn’t be ever shared. To fix this, you could add an error property to the state and update it with a generic error such as ‘Sign up failed — try again’. I’ve added this as an issue in the GitHub repo.

We can try out the signup now and see if it works — navigate to /signup and try entering an email and a sample password — if all is good, you should get redirected to the home page. You can also check that the user information was stored in the Graphcool service (tip: run graphcool playground and click on Data).

Log me in

Now that you have your account created, let’s modify the login component in a similar way we updated the Signup component.

Here’s the mutation we are using:

const AUTHENTICATE_USER_MUTATION = gql`mutation AuthUser($email: String!, $password: String!) {authenticateUser(email: $email, password: $password) {idtoken}}`;

Wrap the component with graphql container:

export default graphql(AUTHENTICATE_USER_MUTATION, {name: 'authenticateUserMutation',})(Login);

And implement the login function that’s pretty much identical with the sign up function (difference being the mutation we call):

Try this out by going to the /login page and using the same email/password you used to sign up. If you get redirected to the home page, it worked!

Issue we have to solve next is the fact that regardless if you’re logged in or not, when you navigate to the home page, you’ll always get the list of links and ability to create links. We should change that, so if you’re not logged in, we render something that gives you links to login and sign up page, and if you are logged in, we will show you the links.

No links for you!

Remember that third query we added earlier, the loggedInUser query? This is what we are going to use. But first, we need to make a change that’s going to attach the user token from the local storage with each GraphQL request we make.

There’s a pattern in Apollo that allows us to do exactly that — with ApolloLink we can inject the authorization token to each request. Install it first:

$ yarn add apollo-link

After you’ve imported ApolloLink from apollo-link, we can create a new instance of it in the index.js file:

We are defining an apolloLinkWithToken in the code above (lines 1–10), then reading our SHORTLY_TOKEN from the local storage, creating the auth header with the token (or null if token is not there) and setting it as an authorization header. Finally, we call the forward function and pass in the updated operation (with the authorization header) — this continues with the chain of calls and eventually the request goes out to the server. Lastly, we need to update the link we are using in line 20 to be the httpLinkWithToken we defined in line 12.

At this point, if we are logged in and we try to call the loggedInUser query we will get back the ID of the logged in user. Let’s add this functionality to the Home.js component. First, this is the query we are going to use:

const LOGGED_IN_USER_QUERY = gql`query CurrentUser {loggedInUser {id}}`;

Notice that we don’t need to provide any parameters to the above query, but when the function behind the query runs, it will check if there was an authorization header attached to it and figure out which user ID corresponds to that token and return us the ID of that user (or nothing, if there was no or invalid auth header provided).

Let’s wrap the component:

export default graphql(LOGGED_IN_USER_QUERY, { name: 'currentUser' })(Home);

And update the render method to render different UI based on the fact if user is logged in or not as well as implement the logout functionality. Here’s the full Home.js file with all these things implemented:

In the render method we wait for the query to execute and then try to get the userId from the query (line 27). Finally, we use a simple if statement to check if we got the user id, then render the component with the logout button and the list of links, otherwise we render a div with links to login and sign up page. Check the screenshots below for both:

View when user is not logged in

Logged-in view with the user Id and a logout button

Yay, this looks much better (from the functional standpoint, not the design standpoint :)). Last thing left is to actually add the relation between User and Link models and modify the queries a bit, so we only display links from the logged in users.

Linking things together

Let’s open the types.graphql file and connect the Link and User models together by adding the bolded lines:

type Link @model {...createdBy: User @relation(name: "UserLinks")...}

type User @model {...links: [Link!]! @relation(name: "UserLinks")...}

The above changes are adding a createdBy field to each link that points to the User that created the link, and a field in User model that gives us an array of all links that the user created.

Since schema changes involve new relations with required values, I am suggesting you delete all links and users you’ve created so far testing this, then run graphcool deploy to deploy the updated schema.

Hopefully deploy worked without issues (if it didn’t, make sure to check what the error is and usually, deleting the old data fixes any issues).

We can start updating the queries now. Let’s start with CreateShortLink.js and CREATE_SHORT_LINK_MUTATION by including the createdBy field in the query:

const CREATE_SHORT_LINK_MUTATION = gql`mutation CreateLinkMutation($url: String!$description: String!** $createdById: ID!**) {createLink(url: $urldescription: $descriptioncreatedById: $createdById) {id}}`;

And passing the user ID we read from the local storage to the mutation in createShortLink function:

await this.props.createShortLinkMutation({variables: {url,description,createdById : localStorage.getItem('SHORTLY_ID')},});

Note: we could have also added the _loggedInUser_ query to the component and use the user ID from that query to create a link. An even better solution would probably be to create a container called _LoggedInContainer_ and use that to wrap all components that we want to use when user is logged in. That way, we do the _loggedInUser_ query on the container level and not separately in each component.

Next up are the queries ALL_LINKS_QUERY and LINKS_SUBSRIPTION in LinkList.js file.

In ALL_LINKS_QUERY we need to filter all links to only those that belong to the logged in user (changes in bold and rest of the query is omitted):

const ALL_LINKS_QUERY = gql`query AllLinksQuery($createdById: ID!) {allLinks**(filter: { createdBy: { id: $createdById } })** {...}}`;

Since we added the createdById field to the filter, we need to update the export statement to pass the id from the local storage to the ALL_LINKS_QUERY like this:

export default graphql(ALL_LINKS_QUERY, {name: 'allLinksQuery',options: props => ({variables: {createdById: localStorage.getItem('SHORTLY_ID'),},}),})(LinkList);

Also, we need to update the filter on our subscription, so it only fires when the user currently logged-in creates or updates a link. Here’s the LINKS_SUBSCRIPTION variable with updates in bold:

subscription NewLinkCreatedSubscription($createdById: ID!) {Link(filter: {mutation_in: [CREATED, UPDATED]node: { createdBy: { id: $createdById } }})...

We also need to pass the createdById variable to the subscription when we call subscribeToMore function in the componentDidMount. Here’s the line to add after the document property:

variables: { createdById: localStorage.getItem('SHORTLY_ID') }

Similarly as with previous queries, we read the user ID from the local storage and pass it as a createdById variable to the document (GraphQL query).

AND WE ARE DONE

At this point you can create multiple users, create links for each of them and verify that you only see the links from the logged in user.

Final demo!

Conclusion

If you got this far — thank you! I hope I was able to provide you with some useful information throughout these series. I’d love to hear your feedback — you can comment on the story below or ping me on Twitter or on GitHub.

If you’re interested in the final code, check the GitHub repo here — feel free to fork the repo and play with the code. I’ll also log some issues on the repo that I’d like to fix in the coming weeks, but if something catches your eye, I’ll be more than happy to look at any pull requests!


Published by HackerNoon on 2018/01/06