Authentication is one of the most important functions in any application. This authentication process has to be secure enough such that data being transmitted should not be compromised. Authentication dictates what users are able to see and do when they log in. Everyone knows how important it is to have a secure authentication method and how it works. I’ll skip that part! Let’s dive right into implementing authentication with Drupal and Gatsby.
Now that we have our OAuth setup ready let's make the required changes on the Gatsby site. I believe that you already have a setup ready to write some code as the blog proceeds.
Before starting to write code we should have in mind what our authentication functionality should be able to perform. Let’s break it down first in small parts:
- User should be able to log in
- User should be able to logout
- Store access token on client-side
- Accessing resource using the access token
- Use the refresh token to get valid access token when the previous access token expires
Let’s create a service which will help us check the following things throughout the project:
- Whether the user is authenticated or not
- If the user is authenticated but access token is expired, then automatically generate new access token so that user experience shouldn’t break.
Some developers prefer to place their helper functions under the ‘services’ folder while the others keep them under ‘utils’. Hence the location of the file doesn’t actually matter, it’s merely individual preference. I am going to create this file under src/services/auth.js
Adding a UI for users to log in
Let's create a new SignIn form component that displays a form users can fill out with a username and password to log in.
That’s all! We have our authentication functionality ready on the site. Now let’s take a look at each function and what it does.
SaveToken(): this function is used to store the token in local storage.
fetchOauthtoken(): This function takes a username and password as parameters, and uses them to make a request to the Drupal /oauth/token endpoint attempting to retrieve a new OAuth token.
fetchSaveOauthToken(): it generates new access token by making use on fetchOauthtoken function. Then stores this token in local storage to access various resources.
drupalLogin(): This function gets invoked when a user tries to log in into the site. It makes a request to /user/login REST resource with username and password. Which returns a user object if the passed credentials are correct else it will return an error message.
handleLogin(): It makes a call to the drupalLogin function to verify that the user exists and provided credentials are right. If a user exists, using fetchSaveOauthToken() generates new access token and stores it in local storage so it can be used for subsequent requests.
isLoggedIn(): This function verifies whether the user accessing the site is authenticated or not. Considering the various scenarios mentioned below it will consider that the user is logged in to the site which means that token is available in local storage.
drupalLogout(): Verifies if a user is logged in or not, if true then it makes a request to Drupal site using the access token to logout out from the site.
handleLogout(): This function makes use of drupalLogout() function and if it is successful then removes the token stored in local storage.
We have a login page and if we try to log in it works perfectly but do you think there is any problem? If yes, then you are right. If you open the Chrome developer console, go to the Network tab, and inspect your OAuth/token request you will be able to see the client ID and client secret in the request header which is not right this can cause a security issue within your site.
How do we resolve this?
Let’s take a look at the options we have in our hand.
- Netlify function
- Overriding OAuth controller
Write simple functions that automatically become APIs. Netlify deploys the functions you write as full API endpoints and will even run them automatically in response to events (like a form submission or a user login). Functions receive request context or event data and return data back to your frontend.
In our case, we get the necessary information i.e. username and password, pass it to the netlify function. This will pre-define our sensitive data in the netlify function which is client ID and client secret and make authentication requests from it to Drupal.
By doing this, only the response will be visible to the user but what is being sent inside the header of the request won’t be available.
One downside to this is that you have to pay as per your usage to use netlify functions.
Let us know if you would like to hear more about netlify dev setup and netlify function from us. We will get back to you with another post talking about it.
Overriding OAuth controller
When you hit a request to /oauth/token endpoint to get an access token this simple_oauth/src/Controller/Oauth2Token.php controller is being executed.
This controller is responsible for generating a token for you based on the headers you sent in the request.
So we thought, what if we override this controller in a custom module and define our client ID and client secret here and when a request comes in and attach these both to the request. The requirement of the Oauth controller to generate a token is being satisfied and our problem of client secret and ID being exposed gets resolved. In addition, we also save some money for our client.
To override the OAuth controller we will have to let Drupal know that when /oauth/token route is requested rather than using a controller provided by a simple OAuth module use the controller from our custom module.
We will have to alter the route so that we can inform Drupal.
Add a YAML file my_module.services.yml into the module's folder with the following content
Now we have to create a Routsubscriber class under my_module/src/Routing where we will notify Drupal to use our custom controller when oauth2_token.token route is requested.
The overridden controller will look like as below
After this, we can remove the following lines from IsLoggedIn and fetchOauthToken function from auth.js file.
Please do let us know if you have any questions or a better approach to achieve this in the comment section.