2018-09-12
Authentication with Amplify in an Electron app
I’ve recently had to implement the authentication page in an Electron app. We’ve decided to go with Amplify. A lot of below code is based on this excellent article: https://blog.ecliptic.io/google-auth-in-electron-a47b773940ae
(def Auth js/amplify.Auth)(def electron (js/require "electron"))(def remote (.-remote electron))(def browser-window (.-BrowserWindow remote))(.configure Auth #js{:Auth #js{:identityPoolId "<aws-identity-pool-id>":region "eu-west-1":mandatorySignIn true}});; I'm using https://github.com/r0man/cljs-http to make requests below:(defn <auth-token"Returns a channel with oAuth access_token"[code](http/post "https://www.googleapis.com/oauth2/v4/token"{:with-credentials? false:json-params {:code code;; If your app is running on a `file://` protocol,;; don't worry. We don't care about where the user;; will be redirected too, as we are closing the window;; before that redirect happens.;; We only want to get the `code` from the resulting;; redirect's url.:redirect_uri "http://localhost:3000":client_id "<google-client-id>":grant_type "authorization_code"}}))(defn <google-profile"Returns a channel with authenticated user's profile"[access_token](http/get "https://www.googleapis.com/userinfo/v2/me"{:with-credentials? false:oauth-token access_token}))(defn- handle-redirect [url callback](let [{:keys [code id_token] :as ks} (parse-url-params url)];; When a user has been redirected to a page with `code`, that means they authenticated;; in the separate Electron window, and that window will now be closed.;; we have to now authenticate the user with Amplify, by fetching the required;; information using the `code` we just got(when code(go (let [;; first, we need the `access_token`:{auth-response :body} (<! (<auth-token code)){:keys [access_token expires_in]} auth-response;; then we need to fetch the user profile from Google, using;; the access_token:{user-profile :body} (<! (when access_token(<google-profile access_token)))];; once we have both, we should use `federatedSignIn` function from;; Amplify to authenticate the user using the AWS IdentityPool(when (and user-profile access_token)(-> (.federatedSignIn Auth "google"(clj->js {:token id_token:expires_at expires_in})(clj->js user-profile))(.then(fn [identity];; at this point with have the Identity from AWS as well;; as `user-profile` with information from Google(callback user-profile))))))))))(defn sign-in"Opens a new Electron window with Google Sign-in/Consent process. After a userhas signed in, the callback will be called with user's profile."[callback](let [win (browser-window.)web-contents (.-webContents win)url (google-auth-url (:google_client_id core/federated-identities-config)"http://localhost:3000")];; We open a new Electron window and watch for redirects. When the redirect happens;; the result URL will have the required information once the user has signed-in(.on web-contents "will-navigate" (fn [_ url](handle-redirect url callback)))(.on web-contents "did-get-redirect-request" (fn [event old-url new-url](handle-redirect new-url callback)))(.loadURL win url)))
After the above sign-in process, you can get the auth information at any point by using Amplify’s Cache function:
(.getItem Cache "federatedInfo")
For example to sign AppSync communication using the authorization token:
(.configure API #js{:aws_appsync_graphqlEndpoint "<appsync-url>":aws_appsync_region "eu-west-1":aws_appsync_authenticationType "OPENID_CONNECT":graphql_headers (fn [](let [token (.-token (.getItem Cache "federatedInfo"))]#js{"Authorization" token}))})