Until now we had log-in security for the Admin module disabled. Inspect /admin/home/index.pug
, and look for the section beginning with 'if (!Cookies.get('auth'))
. Uncomment the section by removing '//'
. Ensure the API service from the previous tutorial is running on port 8081. Return to the home page at http://localhost:8091, and click the 'Admin' menu item to see the login prompt. Use 'demo':'demo'
as username:password and click 'Login'. In VS Code, return to /admin/login/index.pug
. See how we open the modal dialog, bind the form and register the form submit. Modal dialogs are natively supported by the Chrome browser. We use a polyfill (loaded in /\_js/admin.js
) for compatibility with other browsers (Edge and IE need a little bit of CSS love). LoginBusiness
'login()'
contacts the API server (it could be a separate server dedicated to authentication). If the credentials match, LoginBusiness receives an 'OK' response, and we redirect to /admin/home/
. If an authentication error is received, we display it in a 'div'
with id 'error'
on the page.
Along with the 'OK' response, the API server returned a 'token' string. We store the token as a cookie (name: 'auth'
) in the browser. After the initial log-in, we use this token on all pages and for all API calls that require authentication. While we could have stored username and password as a cookie instead, the advantage of using a token after initial authentication is that it can be heavily encoded. For maximum security and defense against network sniffing, the token could even be changed on every request.
To protect the linkblog pages with a log-in, open /admin/linkblog/index.pug
and /detail.pug
and uncomment '//sb.ensureLogin(...)'
. This function checks for the existence of the 'auth'
cookie. If this cookie doesn't exist, it means that the user has not logged in, and he is redirected to the log-in screen. We destroy the 'auth'
cookie on log-out; see 'script.'
in /admin/logout/index.pug
. Log-out is triggered when clicking the admin menu 'Logout' item.
There is one more thing to improve. In our flow, on full page refresh, the browser renders the static parts of the page before ensuring log-in. The ugly result is that the admin menu bar, static page content and footer may briefly display before a redirect to the log-in page happens. This is by design; we allow a CDN to cache the static (non-data) parts of admin module pages. However, we can apply a visual improvement to achieve better transitions. Open _sass/admin.sass
and uncomment the last two lines, beginning with '//body \> *'
. This sets the opacity of the immediate children of the admin HTML 'body'
to a very low '0.1'
by default (You can set it to '0'
, if you prefer). 'Linkblog.ensureLogin'
sets the opacity to '1'
when the user has been confirmed to have successfully logged in, causing the page to display in full. Save admin.sass
and try the log-in routine again to see the result. We could also imagine using a spinner during the transition; see http://spin.js.org/spinner.
For additional security, we send the token along when we make requests to the API server. If the API server is configured to require authentication, we ensure that the specific token received allows reading or writing the data before returning the data. If the token is not valid, we let the API service return a 403 'Forbidden' error. When LinkblogBusiness
receives such an error, we show an alert and redirect to the log-in page. Open /data-topseed-io/config/ApiConfig.js
to configure which API calls are to be secured. In the line for 'get REQUIRE_AUTH'
, uncomment 'write'
so that it reads 'linkblog: ['write']'
. Restart the server in the terminal window. This activates the token check in /data-topseed-io/server/route/LinkblogService.js
in 'router.post('
. The class 'TokenAuth'
contains our very simple authentication functionalities: validate that username:password match demo:demo, and the token always matches "abc".
You just activated token security for the Linkblog
'write'
(=save) function. Let's inspect how the token is passed to the API. In /admin/linkblog/detail.pug
, with '$('#form1').submit({auth: Cookies.get('auth')}, sb.save)'
we instruct to include the auth cookie as event data when calling 'LinkblogBusiness.save'
. From there we pass it on to the DAO in 'sb.linkblogDao.update(formData, e.data.auth)'
. This continues through BDS.js
'update'
, '\_post'
and 'fetch_'
functions, where the token is passed to the API as 'X-JToken' header. If configured as described above, in /data-topseed-io/server/route/LinkblogService.js
the API reads this header and validates the token by calling /route/ds/TokenAuth.js 'isTokenValidPromise'
.
Inspect this function. The returned promise throws an 'invalid token' error in the case of failure, which causes LinkblogService
to abandon the update request and return a 403 error 'Forbidden'.
Reopen /admin/linkblog/LinkblogBusiness.js
and find 'const _updatePromise'
. Here follows what happens in the UI as a result of the update attempt. If the update/save was successful, we redirect to the linkblog list page. If it failed because the token did not validate ('Forbidden'), show an alert and redirect to the login page. In case of other failures, simply show an alert. If you wish to simulate the errors, go to /admin/linkblog/detail.pug
and make it send an invalid'auth'
value with '$('#form1').submit({auth: 'XYZ'}, sb.save)'
. Attempt to add and save a new linkblog item. Revert to '$('#form1').submit({auth: Cookies.get('auth')}, sb.save)'
when done. Since our linkblog is expected to be public, we didn't implement authentication for the 'linkblogDAO.selectList'
API call, but this could easily be added.
This tutorial demonstrated the fundamentals of the two principal authentication flows: log-in and token validation. A 'real life' authentication provider would likely be more advanced than TokenAuth.js
. Tokens might need to be encoded and decoded. A production implementation might access a database of credentials, an in-memory database of valid tokens, or an asynchronously called external authentication service that may return its own promises for credential and token validations. In the shown implementation, successful log-in always redirects to the admin landing page. For production, we would probably enhance the log-in flow by keeping track of which protected page was requested and redirect to it after successful log-in.