Chapter 8. Server-rendered views

This chapter covers

  • Introducing server-rendered views
  • Using server-rendered views in an authentication scheme
  • Setting up the layout page and using partials
  • Using locals to make backend data accessible to views
  • Bootstrapping data directly into views with EJS tags

It’s now a well-known fact that we’re building the greatest virtual edifice to cat videos on the planet Earth, better known as Brushfire. In chapter 6, we embarked on the design and implementation of an identity, authentication, personalization, and access control system, and we established identity with a user model. In chapter 7, we connected that model to frontend elements that enabled a user to create a user record and manage it. Now that a user can create an identity, our next step is deciding how that identity will affect the frontend assets available to the user once they’ve proven they are who they say they are—a process known as authentication.

Definition

When we use the terms authentication or authenticated state, we’re referring to whether a user’s claim, on behalf of a user-agent, is genuine. This is typically referred to as a user being logged in or logged out of an application.

For example, as figure 8.1 shows, if a user is authenticated, we’ll display the user’s Gravatar image and email address in the navigation bar. If the user is not authenticated, we want to display the login form instead.

Figure 8.1. You want to show different assets based on the user authenticated state. Here, if the user is not logged in, you’ll display the log-in form . If the user is logged in, you’ll display their Gravatar image, their email address, and a sign-out button .

The actual mechanics of how a user is authenticated will be implemented in chapter 9, as shown in figure 8.2.

Figure 8.2. The four components of an identity, authentication, personalization and access control system

In this chapter, we’ll make an assumption that you have access to the user’s stored, authenticated state on the backend in the form of a dictionary. This dictionary, named me, will contain an id property whose value is the authenticated user’s record id in the database. If the me dictionary exists, you’ll assume that the user is authenticated. This will let you concentrate on how to communicate the authenticated state to the front-end in a secure and efficient way. The remainder of the chapter will show you how to communicate the authenticated state of the user using the hybrid approach to page navigation in Brushfire.

8.1. Page navigation

Over countless projects, we’ve tried various ways of communicating the authenticated state of a user to the frontend. Ultimately, we found the hybrid approach to page navigation (first mentioned in chapter 1) to be superior when dealing with user authentication, not just because of its SEO advantages, but also because it combines the benefits of server-rendered views with the flexibility of client-side JavaScript frameworks like Angular. Let’s first explore how our current approach to routing and page navigation differs from the hybrid approach.

8.1.1. Client-side vs. server-side routing

So far, the frontend of Brushfire consists of static assets delivered to the browser user-agent and controlled using the Angular framework. We first looked at this approach in chapter 1 using the illustration in figure 8.3.

Figure 8.3. Delivering assets and endpoints to a single-page application

Using the SPA approach, Sails delivers the initial HTML view, JavaScript, and CSS as static assets , and then the JavaScript on the frontend is responsible for making intermediate changes to the view via AJAX requests to the backend API . Wholesale page navigation is controlled by a client-side Angular router . As a user navigates to a different page, client-side routing is used to navigate between pages of Brushfire. These routes are identified by the use of a hashbang (#), and the routes are intercepted by the Angular router. The router then loads templates based on the route into the entry HTML page, as illustrated in figure 8.4.

Figure 8.4. In a single-page app, the entry page is the anchor page where templates are inserted based on frontend routes and the Angular router.

The hybrid approach uses a combination of client-side and server-side rendering of pages controlled by backend Sails routes, as depicted in figure 8.5.

Figure 8.5. Delivering assets and endpoints to a hybrid web application

Using this approach, Sails provides the initial server-rendered view . From this view, client-side JavaScript can update the DOM by making AJAX calls to the Sails backend API . Any wholesale page navigation, however, is handled via server-side routes and routing . Therefore, when a user navigates to a different page, a new server-rendered view is sent as a response . In sum, the overall pages in Brushfire remain the same. The difference is that you’ll now use explicit routes and backend Sails routing to manage changes between the pages, as illustrated in figure 8.6.

Figure 8.6. Using custom routes and backend Sails routing to manage page navigation in Brushfire

Now that you understand the differences between client-side routing and backend routing, how does this affect your ability to get the information contained in the me dictionary to the frontend? The advantage of combining this type of routing with server-rendered views is that you can control the personalization of the page based on the user’s authenticated state. Therefore, you can place the contents of the me dictionary directly in the view.

8.1.2. What is a server-rendered view?

Server-rendered views are the result of Sails merging one or more markup templates from the brushfire/views/ folder. The result, illustrated in figure 8.7, is transformed into HTML before being sent to the browser to be displayed.

Figure 8.7. An incoming GET request to / matches an explicit route that triggers a view named homepage.ejs to be rendered into homepage.html and sent as a response to the requesting user-agent.

The real power of server-rendered views is their ability, using special inserted tags, to merge data from the backend into the view. Coupled with Sails’ ability to make data available to a view from an explicit route, you now have a way to securely inject values or other templates into a view on the backend before they’re sent to the browser for display (as illustrated in figure 8.8).

Figure 8.8. An incoming GET request to / B matches an explicit route and passes a locals dictionary c to a view named homepage.ejs d. The view contains a special tag that, when rendered, inserts a value from the locals dictionary as a response e to the requesting user-agent.

It’s always easier to understand the process when working through an actual example. So, let’s get started implementing this hybrid approach.

8.1.3. Obtaining the example materials for this chapter

Once again, in an effort to cut down on the number of pages dedicated to source code, we’ve created a GitHub repo with new assets. You can navigate directly to the repo here, https://github.com/sailsinaction/brushfire-ch8-start, or via a link from the chapter 8 hub at http://sailsinaction.github.io/chapter-8/. After cloning the repo, install the Node module dependencies via npm install.

Tip

If you do choose the cloning option, don’t forget to add the brushfire/config/local.js file with your Google API key from chapter 5 (section 5.4.6) as well as to start your local PostgreSQL brushfire database from chapter 6 (section 6.4.2).

8.2. Personalizing web pages

Now that you have a better understanding of server-rendered views and the hybrid approach, let’s see how you can use them to personalize HTML with data about a logged-in user. To do this, you’ll take several steps that include the following:

  • Get a better understanding of explicit custom routes.
  • Create a custom route that triggers the homepage view.
  • Explore how to combine various server-rendered views using the Embedded JavaScript (EJS) template engine, layouts, and tags.
  • Simulate bootstrapping of the me dictionary on a browser’s window dictionary using markup in the layout template.
  • Add EJS tags to the layout template to make the me dictionary contents dynamic.
  • Pass the me dictionary values in your custom route to the markup in the layout template.

8.2.1. A review of explicit routes

So far, in Brushfire you’ve relied on Sails asset routes and blueprint routes to trigger the required endpoints and responses from your requests. You learned in chapter 1 that the Sails server listens for incoming requests and, in addition to blueprint routes and asset routes, looks for a matching explicit route in brushfire/config/routes.js, similar to figure 8.9.

Figure 8.9. How the Sails router matches the incoming request with routes to trigger an action

You also learned that the route itself is implemented in JavaScript as a dictionary, depicted in figure 8.10.

Figure 8.10. An explicit route is a JavaScript dictionary made up of a route address and a target.

The key of the dictionary is the request or route address , consisting of the HTTP method and path, and the value is another dictionary called the target , that consists of a controller, UserController, and an action, signup.

Note

Shortly, you’ll see that the target can also be a call to execute a server-rendered view.

The Sails router listens for incoming requests and first tries to match the request with an explicit route. If it finds one, the router has done its job and listens for another request. If it’s unable to find an explicit route, it tries to match the request with one of the blueprint routes. If it’s unable to find a blueprint route, it tries to match the request with an asset route. Finally, if no route is found, Sails will display the 404 Not Found status.

Note

The router will also look to see whether the request matches the CSRF token path, but we’ll address this potential security issue in chapter 15.

8.2.2. Defining an explicit route

In chapter 3, you used a generator to remove the custom default route to the home-page and to create an index.html file in brushfire/assets/index.html. From that point on, when a browser user-agent made a GET request to / (slash), Sails first looked for a custom route in brushfire/config/routes.js, and, not finding one, it next looked in brushfire/assets/ for an index.html file. This index.html file became the entry page of your application. You’re now going to use a hybrid approach where Sails will return to a server-rendered entry page on the backend, with Angular controlling the contents of the page on the frontend. So, you’ll override brushfire/assets/index.html by adding a custom route with the same route address. Open /brushfire/config/routes.js, and add the following code.

Example 8.1. Triggering the homepage via a custom route

You’ll notice that the route doesn’t trigger a controller action but instead contains a dictionary that identifies the view that should be rendered when the route is triggered. Therefore, custom routes can trigger both controller actions and views. In your custom route, a GET request to / will now trigger the display of a homepage template located in the brushfire/views/homepage.ejs folder. Why are you using the .ejs file extension? EJS is one of many view engines you can use with Sails.

Definition

View engines, also called template engines, are what combine the templates and data to produce HTML. By default, Sails uses the EJS template engine.

Views are defined by EJS templates located in brushfire/views/. When called on, the view engine combines the templates, which results in HTML. Now that you can trigger the homepage view with a request via a custom route, let’s take a closer look at the view itself.

8.2.3. Using EJS views

In Sublime, open brushfire/views/homepage.ejs, and you should see something similar to the following listing.

Example 8.2. The homepage EJS template
<div ng-controller="homePageController" class=" col-md-8 col-md-offset-2">
  <div class="jumbotron">
    <h1 class="jumboHeading">Chad's Feline Viral Videos Emporium</h1>
    <h2 class="jumboSubHeading">Viral cat videos since 2015</h2>
    <div><img src="./images/cat_logo.png" /></div>
    <a ng-hide="me.id || false" href="/signup" class="btn btn-lg btn-
     success">Sign up now!</a>
  </div>
</div>

The first thing that’s immediately apparent is that this isn’t a complete HTML page. This is only a part of what will make up the homepage view. EJS templates can use a base layout template that merges other templates to form a single page. Let’s look at the layout template next.

8.2.4. Using partials and layout.ejs

When building an application with many different pages, it can be helpful to extrapolate markup shared by several HTML files into a layout. This reduces the total amount of code in your project and helps you avoid making the same changes in multiple files down the road. For example, in chapter 3, you learned to use special tags like <!--STYLES--> <!—STYLES END--> in brushfire/assets/index.html as placeholders for links inserted automatically when Sails starts via sails lift. Sails replaced these tags with links to files in the brushfire/assets/ folders. Let’s review the tags in table 8.1.

Table 8.1. Automated linking to external CSS and JavaScript files via tags

Tag

Folder

Example

<!--STYLES--> <!—STYLES END--> brushfire/assets/styles/bootstrap.min.css <link rel="stylesheet" href="/styles/bootstrap.min.css">
<!--SCRIPTS--> <!--SCRIPTS END--> brushfire/assets/js/app.js <script src="/js/app.js"></script>

Instead of including the tags directly in each view, we’ve placed them in a layout view provided with the chapter 8 GitHub repo. In Sublime, open the layout in brushfire/views/layout.ejs, and you’ll see something similar to the next listing.

Example 8.3. The layout file for Brushfire

The layout file also contains a variable named body that’s surrounded by yet another type of EJS tag syntax.

Tip

Don’t confuse these tags with the previously mentioned Sails link tags like <!--STYLES-->; the EJS template engine has its own template tags.

There are three types of EJS template tags. It’s important to understand the different ways they render the values between the opening and closing tags. Table 8.2 provides example usage of each tag as well as the resulting rendered value.

Table 8.2. EJS template tags

EJS tag example

Rendered result

<%= <script> alert("Howdy") </script> %> &lt;script&gt; alert(&quot;Howdy&quot;) &lt;/script&gt;
<%- "<script> alert('Howdy') </script>" %>
(<% if (!loggedIn) { %> <a>Logout</a> <% } %> If loggedIn is true, <a>Logout</a> will be displayed. If not, nothing will be rendered.

Using an opening <%= (equals) template tag with a closing %> tag, HTML escapes whatever is contained between the tags and then includes it as a string.

Note

HTML escaping replaces certain characters and replaces them with their encoding equivalent, like the less-than (<) sign &lt;. This prevents the browser from interpreting a string as JavaScript but still displays the correct characters.

This is an important protection against cross-site scripting (XSS) attacks.

Note

XSS attacks occur when a user enters malicious scripts that are then executed by another user. For example, if a user enters a malicious script in a form field and the value is not stripped of script tags, the script will execute when the value is displayed. It’s important to know where your values originate. If you don’t have control over where the values originate, you’ll display them using HTML escaping.

Using an opening <%- (dash) template tag with a closing %> tag includes whatever is contained between the tags as is, without escaping it. If you decide to use this, be very careful because it can make your application vulnerable to XSS attacks. There are situations where this tag will be both useful and safe. In fact, you’ve already used the tag in listing 8.3 where you inserted the value of body, and when incorporating partials.

Definition

Partials are template fragments you can incorporate into views. Common examples of partials are headers and footers.

Using an opening <% template tag with a closing %> tag executes any JavaScript between the tags when the template is being compiled. Unlike the other two tags, whatever value is compiled isn’t displayed in the markup. This template tag is useful for inserting conditionals like if/else and looping over data using for/each.

So, in listing 8.3, body and partial('./partials/navigation.ejs') are replaced by whatever view template is being rendered within the layout file as well as the template in the partial’s path. In your custom route, the homepage template and the partials/navigation template are combined with the layout template to form a view similar to figure 8.11.

Figure 8.11. The layout file contains the navigation partial and surrounds the view that’s being rendered.

Let’s see this in action. Restart Sails using sails lift and navigate your browser to localhost:1337; your browser should look similar to figure 8.12.

Figure 8.12. The new navigation section contains both the authenticated and unauthenticated markup states.

The layout wrapped the homepage to deliver the server-rendered view in figure 8.12, but you do have an issue. The layout is displaying the markup for both authenticated and unauthenticated states. Your intent is to display the sign-in form when the user is not authenticated and therefore not logged in. When the user is authenticated, you want to hide the sign-in form and display the user’s authenticated markup: the Gravatar image, email address, and logout link.

The navigation template contains Angular directives that control the display of the markup based on the me dictionary, which contains the user’s authenticated state. In Sublime, open brushfire/views/partials/navigation.ejs to see the directives in the following listing.

Example 8.4. The navigation partial

If me.id has a value, the sign-in form will be hidden and the Gravatar image, email address, and logout link will be displayed. If me.id is null, the sign-in form will be displayed and the authenticated markup will be hidden. How can the frontend get access to the values of the me dictionary? First, you’ll bootstrap the me dictionary on the browser’s window dictionary via the layout view.

8.2.5. Exposing data for use in client-side JavaScript

Let’s add the me dictionary to the browser’s window dictionary in the layout view. Once again, open brushfire/views/layout.ejs in Sublime, and add the following code.

Example 8.5. Bootstrapping data in layout.ejs

For now, you’ve hardcoded the value of me.id to null. Next, let’s look at your Angular controller for the layout view. In Sublime, open brushfire/assets/js/controllers/navPageController.js, and examine the $scope.me property in the next listing.

Example 8.6. Bootstrapping data on view

The Angular controller’s main function is to grab the value of the me dictionary from window.SAILS_LOCALS. By hardcoding the value of me.id equal to null, you simulate a logged-out user. The Angular directives will hide the authenticated markup and show the sign-in form. Restart Sails using sails lift, and navigate your browser to localhost:1337, which should display something similar to figure 8.13.

Figure 8.13. The homepage view with the sign-in form displayed in its unauthenticated state

The homepage view is displayed with the sign-in form in the navigation portion of the markup indicating an unauthenticated state. Next, let’s use Sails locals to pass a value from the backend to the me.id property in your explicit route.

You’ve already hardcoded the value of me.id to null, and you saw how the Angular controller could easily grab data from the browser’s window dictionary and save it on $scope.me. Then Angular directives can use that information to display markup based on the values found in me. Now you want to make the values you bootstrap on the page dynamic. You’ll use EJS template tags to inject the values of the me dictionary from locals passed in an explicit route.

In Sublime, open brushfire/views/layout.ejs, and use the template tags shown here, so that the locals passed in the route will be compiled on the view.

Example 8.7. Using EJS template tags to incorporate locals from the route

Next, you’ll add the locals to the explicit route to pass the authenticated state to the layout view.

8.2.6. Hardcoding locals in a route

You’ve seen how Sails makes data available to views with variables called locals. In chapter 9, you’ll transition to setting locals in your controller actions. For now, just so you get to see some action, you’ll pass through stub data in your explicit route. In Sublime, open brushfire/config/routes.js, and add the following locals to the GET / route.

Example 8.8. Adding locals to a view in your custom route

Here, you pass a static dictionary of a simulated user to the view via locals. Figure 8.14 illustrates the compiling process where locals passed in the route are injected into a view to produce your server-rendered view.

Figure 8.14. Using locals through a custom route to bootstrap data in a server-rendered view

The route triggers the view along with locals. You use template tags that will stringify the value of the me dictionary or insert null if the me local is empty . The result of this compile is a server-rendered view that contains the me dictionary . The me dictionary will inform the frontend whether the user who made the request is authenticated or unauthenticated. Let’s see this in action. Restart Sails using sails lift, and navigate your browser to localhost:1337. Your browser should look similar to figure 8.15.

Figure 8.15. The homepage view with the simulated authenticated state displaying the authenticated markup

Notice that the sign-up button isn’t visible because the ng-hide directive hides the sign-up button based on the me dictionary if the user is authenticated. Now that you understand how server-rendered views and frontend frameworks like Angular can be used to control what’s displayed on the frontend, let’s take a look at the remaining pages of Brushfire.

8.3. Transitioning from an SPA

If you’re following along in the example repo, you’ll notice that we provided views for each of the main frontend pages of Brushfire outlined in figure 8.16.

Figure 8.16. The relationship between the layout view and the other views of Brushfire

This allows you to share the same layout, partials, and (to some degree) locals, across each of your different views. Currently, your videos.ejs view is served by an explicit route that simulates an unauthenticated state of a dummy user. And each of the other views has a corresponding custom route in brushfire/config/routes.js that simulates an authenticated state, similar to the following listing.

Example 8.9. locals used to simulate the authenticated state of a user
...
locals: {
      me: {
        id: 1,
        gravatarURL:
         'http://www.gravatar.com/avatar/ef3eac6c71fdf24b13db12d8ff8d1264?',
        email: '[email protected]'
      }
    }
...

Table 8.3 lists the remaining routes and corresponding views we provided in the GitHub repo. In chapter 9, when we cover authentication, we’ll replace this fake data with data about the currently logged-in user.

Table 8.3. Brushfire custom routes and templates

The route

The view template

'GET /videos' brushfire/views/videos.ejs
'GET /profile' brushfire/views/profile.ejs
'GET /edit-profile' brushfire/views/edit-profile.ejs
'GET /signup' brushfire/views/signup.ejs
'GET /restore-profile' brushfire/views/restore-profile.ejs
'GET /administration' brushfire/views/administration.ejs

Let’s see this in action. Restart Sails via sails lift, and navigate your browser to localhost:1337/videos. Your browser should look similar to figure 8.17.

Figure 8.17. This videos page simulates an unauthenticated user and therefore doesn’t have the markup to add a video.

You might be wondering whether you could add a video with a POST request to /videos in an application like Postman even if you weren’t authenticated. The answer is yes. It’s important to distinguish between controlling what’s displayed on the frontend with preventing access to controller actions on the backend. In chapter 10, we’ll show you how to lock down access to controller actions with policies based on a user’s authenticated state. For now, we’re concentrating on managing what’s displayed on the frontend, based on the user’s authenticated state.

You now have a personalization system for your frontend based on the user’s simulated authenticated state. In chapter 9, you’ll build out the components of a user authentication system. You’ll then transition the frontend that’s using a simulated authenticated state to the live authentication system that utilizes what you’ve built here to personalize the frontend.

8.4. Summary

  • Sails can be configured to utilize client-side Angular routes or Sails backend routes for page navigation.
  • Sails uses the EJS view engine to integrate templates using a layout view with EJS tags.
  • Custom routes that pass a dictionary can simulate a user authenticated state to the layout view.
  • EJS tags are used to dynamically inject the user’s simulated authenticated state on the view.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset