Skip to content

Latest commit

 

History

History
203 lines (164 loc) · 8.52 KB

README.md

File metadata and controls

203 lines (164 loc) · 8.52 KB

Web Package for RedwoodJS dbAuth OAuth Plugin

Table of Contents:

Overview

Welcome to the web package of the RedwoodJS dbAuth OAuth Plugin. It handles the client-side logic of the OAuth process, providing a smooth user experience.

The web package integrates with the auth provider of your RedwoodJS application, creating the AuthProvider component and the useAuth hook. It also initializes the OAuthProvider and useOAuth in the same way, allowing easy access to the auth context.

Get started with the setup instructions below and take a step towards simplifying your application's authentication flow. Happy coding!

Setup Instructions

Adding the package

If you haven't already added the packages to your web and api workspaces, we'll start off by adding the web side package. From the root directory of your RedwoodJS app, run: yarn workspace web add @spoonjoy/redwoodjs-dbauth-oauth-web

Web-side auth updates

Navigate to web/src/auth.ts.

This is where your dbAuth client is created, and the AuthProvider wrapper and useAuth hook are being exported.

We're going to do something very similar - go ahead and add the following import statement:

import {
  createOAuthClient,
  createOAuth,
} from '@spoonjoy/redwoodjs-dbauth-oauth-web'

And then, add the following:

/**
 * Note that this second parameter to `createOAuth` is a function that tells the OAuth package how to handle error messages. Here, I'm simply taking the error message and toasting it.
 * If you'd like to do the same, go ahead and add the following import statement:
 * - `import { toast } from '@redwoodjs/web/toast'`
 */
const onOAuthError = (error: string) => {
  toast.error(error)
}

const oAuthClient = createOAuthClient({
  enabledProviders: { apple: false, github: false, google: false },
})

export const { OAuthProvider, useOAuth } = createOAuth(
  oAuthClient,
  onOAuthError
)

App.tsx updates

In web/src/App.tsx, you need to add the OAuthProvider that we just exported in the last step. It's the exact same usage as dbAuth's AuthProvider:

...
-import { AuthProvider, useAuth } from './auth'
+import { AuthProvider, useAuth, OAuthProvider } from './auth'
+import '@spoonjoy/redwoodjs-dbauth-oauth-web/dist/style.css' // only required if you're planning on using the supplied button components
 
 import './scaffold.css'
 import './index.css'
@@ -13,9 +13,11 @@ const App = () => (
   <FatalErrorBoundary page={FatalErrorPage}>
     <RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
       <AuthProvider>
-        <RedwoodApolloProvider useAuth={useAuth}>
-          <Routes />
-        </RedwoodApolloProvider>
+        <OAuthProvider>
+          <RedwoodApolloProvider useAuth={useAuth}>
+            <Routes />
+          </RedwoodApolloProvider>
+        </OAuthProvider>
       </AuthProvider>
     </RedwoodProvider>
   </FatalErrorBoundary>
...

Usage

Because I've accidentally created new accounts on apps that support signing in with providers countless times, your users can't accidentally create a new account when using this library.

What this means is that there's three types of interactions:

  • link: Link an existing account to a new OAuth provider
  • signup: Sign up using with a new OAuth provider
  • login: Log in with an existing OAuth provider

For all three of these, the flows start off by redirecting the user to sign in with the given provider, which then redirects them to your API, which then returns a redirect back to your application. Therefore, the steps to beginning any of these flows looks very similar.

Logging in and signing up

These two flows are the simplest and easiest. First, make sure you've followed the Provider Onboarding instructions and registered the required redirect URIs with that provider - if you don't the user will see an error when they try to log into that provider, something along the lines of "redirect URI mismatch".

Then, get the OAuth URLs for kicking off these flows as follows:

import { useAuth, useOAuth } from 'src/auth'

const { getOAuthUrls } = useOAuth()
const oAuthUrls = getOAuthUrls({
  method: 'login', // or 'signup'
})

oAuthUrls will now be a map with the URLs for kicking off these flows for the providers that you've enabled. You can use it, for example, as follows:

Array.from(oAuthUrls.entries()).map(([provider, url]) => {
  // your code for rendering this link
})

So that you don't need to worry about spending time on these buttons, this library provides buttons that you can use. A complete setup for the login and signup flows looks like this:

import { OAuthButtons } from '@spoonjoy/redwoodjs-dbauth-oauth-web'
import { useOAuth } from 'src/auth'

const YourComponent = () => {
  const { getOAuthUrls } = useOAuth()

  return (
    <OAuthButtons
      action="login" // or 'signup'
      getOAuthUrls={getOAuthUrls}
    />
  )
}

I just added this to my scaffolded login/signup pages, and I'm currently working on making these the preferred way of logging in/signing up (as opposed to just rendering the buttons above/below the username/password fields).

It'll only render buttons for your enabled providers, so if you don't see any changes to your UI yet, that's probably why.

Linking and unlinking accounts

While linking accounts is very similar to logging in and signing up, unlinking is a bit different because there's no redirects involved. This is because to unlink an account, we don't interact with the provider at all - we just delete the record from the database.

Additionally, because we want to make it clear what providers have already been linked, useOAuth provides two extra functions for this use case:

  • unlinkAccount
    • You can use this as the onClick handler for a button, for example onClick={async () => { await unlinkAccount('apple') }}
  • getConnectedAccounts
    • Returns an array of the Connected Account records, which includes the following information (more info on the API readme):
      • provider: Provider (as a string literal)
      • providerUserId: string
      • providerUsername: string
      • userId: string
      • createdAt: Date

I made a Settings page for containing this and any other user-configurable things. If you don't already have somewhere like this in your app, generate a new page by running: yarn rw g page Settings

If you want to build your own button for handling this, take a look at how the LinkOAuth component included in this library is built. But if you want to just use ours, setup is super easy. In your Settings page (or wherever), do the following:

import { OAuthButtons } from '@spoonjoy/redwoodjs-dbauth-oauth-web'
import { useOAuth } from 'src/auth'

// optional, see below
import { toast } from '@redwoodjs/web/toast'

const YourComponent = () => {
  const { getOAuthUrls, unlinkAccount, getConnectedAccounts } = useOAuth()

  return (
    <OAuthButtons
      action="link"
      getOAuthUrls={getOAuthUrls}
      // required when `action` is `link`
      linkOAuthConfig={{
        unlinkAccount,
        getConnectedAccounts,
        // the below three parameters are optional
        onLinkSuccess(linkedAccount) {
          toast.success(`successfully connected ${linkedAccount}`)
        },
        onUnlinkSuccess(unlinkedAccount) {
          toast.success(`successfully disconnected ${unlinkedAccount}`)
        },
        // Optional callback to run when linking fails. Because unlinking is a request from the client, rather than a redirect (like linking), the `onOAuthError` callback that we created in `web/src/auth.ts` isn't called in this flow.
        // This will probably be removed in future versions.
        onUnlinkError(provider, error) {
          toast.error(`error disconnecting ${provider}: ${error}`)
        },
      }}
    />
  )
}

All done!

And that's it! If you haven't yet set up the api side, go check out those instructions.

Next steps

Now that you've set up both your web and api sides, it's time to enable OAuth provider(s)!