AWS Amplify and Clojurescript Re-Frame Part 1

Robert J. Berger
Omnyway
Published in
9 min readDec 27, 2019

Add Serverless Authentication with Almost No Code

AWS Amplify

AWS Amplify makes it super simple to build Web and Mobile Apps that use the entire suite of AWS Services with minimal coding by the app developer. Clojurescript and Re-Frame make creating SPA apps a joy. The combination of all three is truly a superpower!

The AWS Amplify CLI makes it easy to setup various AWS services and make them available via the Amplify SDK to your Web or Mobile App. It also has great support for React based apps.

The goal of this project is to show how to be able to use AWS Amplify with Clojurescript / Re-Frame Web Apps.

The initial AWS feature to incorporate is the Amplify Authentication service which has AWS Cognito at its core. Amplify offers a React Higher Order Component that allows you to wrap your JS app with Authentication.

  • Ensures that your App can only be accessed when authenticated
  • Handles all the UI and API integration with Cognito
  • You do not have to write any of the code
  • Sign-up
  • Verify phone_number or email address
  • Sign-in
  • Sign-out
  • Forgot Password
  • Password reset

Optionally (and not described in this first article but easy to add)

  • Customize Authenticator UI
  • Use your own UI components
  • AWS Auth for AWS services which require signing requests.
  • Social Provider Federation / OAuth
  • Force new password
  • Multi-Factor Authentication
  • User Attributes
  • Lambda Triggers

Note: This article and its followup AWS Amplify and Clojurescript Re-Frame Part 2: Deploy to Production Cloudfront are based on branch basic-amplify-with-cloudfront of the omnyway-labs/amplify_cljs_example repo.

Re-Frame / Clojurescript / Shadow-cljs

The main goal of this project is to show how to leverage Amplify in a Clojurescript Single Page App ( SPA). In particular, using the re-frame Clojurescript Library.

Re-frame is a functional framework for building SPAs leveraging Reagent. Reagent is a minimalistic interface between ClojureScript and React.

Clojurescript is hosted on Javascript and can naturally interoperate with Javascript in general. This project uses shadow-cljs as the buildtool. Its major superpower is to make it pretty easy to leverage node.js packages. The main features it offers include:

  • Good configuration defaults so you don’t have to sweat the details
  • Seamless npm integration
  • Fast builds, reliable caching, …
  • Supporting various targets :browser, :node-script, :npm-module, :react-native, :chrome-extension, …
  • Live Reload (CLJS + CSS)
  • CLJS REPL
  • Code splitting (via :modules)

Put together, these tools allows one to build SPAs in a functional / Clojure centric way, but still leverage all the wonders of React and node.

That being said. There are a lot of learning curves that also need to be climbed to make it all work together.

This project is an attempt to make it easier for folks to jump right in. It can be used as a starting point to build your own SPA with sophisticated Authentication services.

This article and the associated github repo
omnyway-labs/amplify_cljs_example
should tell you everything you need from scratch to get you to a working basis of a re-frame SPA running on your local machine with full AWS Cognito Simple Authentication backend. The amount of code you have to write to make it work is surprisingly trivial due to the scaffolding features of re-frame and the AWS generation / auto-configuration of Amplify.

The second article in this series Deploy to CloudFront with Amplify Console shows how to deploy the SPA to AWS CloudFront with minimal effort.

Assumptions

You have already installed the following on your laptop / dev machine:

Initial Setup

We’re using the re-frame scaffold template to create the initial app. We’re not going to add much to the app in this article other than the Amplify Authentication wrapper.

Later articles will add more functionality so we’re including some extras like garden for doing CSS in clojure. More importantly we’re including re-com Which is a library of ClojureScript UI components, built on top of Reagent. It integrates with re-frame in a very functional / clojure way.

10x is a very nice debugging dashboard for re-frame. Again, won’t be using it until later articles but its something you will want to have if you use this for building on top of.

Generate the scaffold

lein new re-frame amplify_cljs_example +10x +cider +re-com +test \
+garden
cd amplify_cljs_example
lein garden once
git init

Update .gitignore by adding:

/resources/public/css/screen.css .shadow-cljs node_modules
.shadow-cljs
node_modules

More setup; build and run the local instance of the initial scaffolded SPA

git add -A 
git commit -a -m "Initial commit after lein new re-frame amplify_cljs_example +10x +cider +re-com +test +garden"
npm install
git add package-lock.json
git commit -m "package-lock for npm" package-lock.json
lein garden once
lein dev

The lein dev will build and run the application locally. It doesn't exit, it keeps running, monitoring the app files. If there are any changes it will automatically hot load the application. Lein is effectively running shadow-cljs watch app

  • The lein dev probably added more packages for npm and so you should update your git in another shell window in the same directory
git commit -m "lein updates to package.json" package-lock.json \
package.json

Ensure the basics are working

So if the lein dev is running properly, it will end with:

[:app] Build completed. (1659 files, 1658 compiled, 0 warnings, 46.89s)

At that point, you should be able to:

  • Go to http://localhost:8280 in a browser (Chrome is optimal)
  • Should look something like:

Set up AWS Amplify

  • You should be in the top level of the amplify_cljs_example directory
  • Assuming you don’t already have AWS Amplify installed on your dev machine:
npm install -g @aws-amplify/cli 
amplify configure

Initialize the Amplify project.

  • Mostly accept the defaults or as appropriate for you
  • Start command : lein dev
  • AWS Profile yes and use the one you created when you did amplify configure which by default is default
> amplify init 
Scanning for plugins...
Plugin scan successful
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project reframerecomamplifye
? Enter a name for the environment dev
? Choose your default editor: Emacs (via Terminal, Mac OS only)
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path: src
? Distribution Directory Path: build
? Build Command: lein prod
? Start Command: lein dev
Using default provider awscloudformation

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use default
⠧ Initializing project in the cloud...
...Your project has been successfully initialized and connected to the cloud!

Add the Amplify Authentication service

  • Details at AWS Amplify Authentication
  • Sign-in: Selecting ‘Email’ and/or ‘Phone Number’ will allow end users to sign-up using these values. Selecting ‘Username’ will require a unique username for users.
> amplify add auth 
Using service: Cognito, provided by: awscloudformation
The current configured provider is Amazon Cognito.
Do you want to use the default authentication and security configuration? Default configuration
Warning: you will not be able to edit these selections.
How do you want users to be able to sign in? Email
Do you want to configure advanced settings? No, I am done.
Successfully added resource reframerecomamplifye16cd456a locally

Push the auth feature / config to AWS

> amplify auth push

Current Environment: dev
...? Are you sure you want to continue? Yes
⠹ Updating resources in the cloud. This may take a few minutes...
...✔ All resources are updated in the cloud

Install the Amplify Node modules we’ll be using for Authentication

npm add aws-amplify aws-amplify-react

Copy the css for the amplify UI widgets into the proper resource for our app’s css to be picked up

  • Note: I was looking for a better way to do this that is more via configuration. If anyone knows a better way please let me know
cp node_modules/@aws-amplify/ui/dist/style.css \
resources/public/css/aws-amplify-ui-style.css

Update resources/public/index.html to incorporate that css by adding the following line in the <head> section

<link href="css/aws-amplify-ui-style.css" rel="stylesheet" type="text/css">

Get the changes into git

git add amplify resources/public/css/aws-amplify-ui-style.css 
git commit -m "Added and configured Amplify" \
.gitignore package-lock.json package.json amplify \
resources/public/index.html resources/public/css

Update the code to enable the Authenticator

Edit src/cljs/re_frame_re_com_amplify_exp/core.cljs

  • Add the following lines to the require block. This is what will import the node js into your Clojurescript
["aws-amplify" :default Amplify :as amp]
["aws-amplify-react" :refer (withAuthenticator)]
["/aws-exports.js" :default aws-exports]

Add the following def after dev-setup function

This is where you are wrapping the Amplify HOC around your application. The withAuthenticator is the HOC function being pulled in from the Amplify Auth SDK. It ensures that your app only is accessed after authentication and it handles all the Frontend UI and communication with the Backend to implement the full suite of Authentication.

(def root-view
(reagent/adapt-react-class
(withAuthenticator
(reagent/reactify-component views/main-panel) true)))

Change the function mount-root to be:

(defn ^:dev/after-load mount-root []
(re-frame/clear-subscription-cache!)
(.configure Amplify aws-exports)
(re-frame/dispatch-sync [::events/initialize-db])
(reagent/render [root-view]
(.getElementById js/document "app")))

Remove the following line from init function:

This makes it so that the re-frame db is inited after the cache is cleared. As far as I can tell the Scaffolding puts it in the wrong place

(re-frame/dispatch-sync [::events/initialize-db])

The final file should be:

(ns amplify_cljs_example.core
(:require
[reagent.core :as reagent]
[re-frame.core :as re-frame]
[amplify_cljs_example.events :as events]
[amplify_cljs_example.views :as views]
[amplify_cljs_example.config :as config]
["aws-amplify" :default Amplify :as amp]
["aws-amplify-react" :refer (withAuthenticator)]
["/aws-exports.js" :default aws-exports]
))

(defn dev-setup []
(when config/debug?
(println "dev mode")))

(def root-view
(reagent/adapt-react-class
(withAuthenticator
(reagent/reactify-component views/main-panel) true)))

(defn ^:dev/after-load mount-root []
(re-frame/clear-subscription-cache!)
(.configure Amplify aws-exports)
(re-frame/dispatch-sync [::events/initialize-db])
(reagent/render [root-view]
(.getElementById js/document "app")))

(defn init []
(dev-setup)
(mount-root))

The display should look like this:

You should click the button on the debugger to have it be in its own window as it will block the “sign-out” button later

Commit the changes to git

git commit -m "App updated to require Cognito Authenticator before \
any other operation" src/cljs/re_frame_re_com_amplify_exp/core.cljs

Wrap Up

You now have a template of how to create a basic Re-Frame SPA with authentication from AWS Amplify with almost not coding and without having to set up your own backend authentication infrastructure. You can continue with this to use other AWS Amplify authentication features (Social Provider Federation, Multi-factor auth, etc) as well as the other Amplify services such as graphql access to DynamoDB, Push notifications, etc.).

The next article, AWS Amplify and Clojurescript Re-Frame Part 2: Deploy to Production Cloudfront, will show how to deploy it to “production” on AWS Cloudfront using Amplify Console so that the Re-Frame SPA can be used on the general Internet also without you having to set up any servers yourself.

Originally published at https://www.omnyway.com on December 27, 2019.

--

--