@spoonjoy/redwoodjs-dbauth-oauth-api
v2.0.1
Published
Server-side OAuth communication and database management for RedwoodJS projects using dbAuth. Follows the dbAuth integration pattern and reuses some of its functionality and configurations.
Downloads
18
Readme
API Package for RedwoodJS dbAuth OAuth Plugin
Table of Contents:
Overview
Welcome to the heart of the RedwoodJS dbAuth OAuth Plugin - the api
package. This package is engineered to handle the server-side logic of your OAuth authentication process.
Whether you're a seasoned developer looking to streamline your OAuth integration or a beginner just getting started, this package is designed with simplicity and efficiency in mind. By closely following the dbAuth integration pattern, it initializes a class, OAuthHandler
, that encapsulates all the OAuth business logic and configuration parameters. This package also leverages the DbAuthHandler
instance, reusing its functionality and configurations to ensure a seamless and efficient authentication process.
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 api
side package. From the root directory of your RedwoodJS app, run:
yarn workspace api add @spoonjoy/redwoodjs-dbauth-oauth-api
Preparing the database
First, we need to add the model for storing the linked OAuth provider information in the database. Go ahead and paste this wherever you like in api/db/schema.prisma
:
model OAuth {
provider String
// The ID of the user on the OAuth provider's system
providerUserId String
// The username of the user on the OAuth provider's system. Useful for helping users identify their linked accounts
providerUsername String
// The below two fields should be in reference to whatever your user model is
user User @relation(fields: [userId], references: [id])
userId String
createdAt DateTime @default(now())
@@unique([provider, providerUserId])
@@unique([userId, provider])
@@index([userId])
}
You'll also want to add an OAuth
relation to the User
model, as well as its previously required hashedPassword and salt fields optional (since users may want to only authenticate via OAuth, they'll never get to enter a password):
model User {
id Int @id @default(autoincrement())
email String @unique
- hashedPassword String
+ hashedPassword String?
- salt String
+ salt String?
+ OAuth OAuth[]
...
}
!!Important - it's not just the password fields that need to be optional, it's any field that isn't:
id
email
(even if it's not used as username)- your username field
If you have any other required fields, it'll fail when trying to create a new user.
Then, run yarn rw prisma migrate dev
, and give it a name like "prepare for OAuth".
That's it! Onto the next section.
Updating the api auth
function
Navigate to api/src/functions/auth.ts
.
Your current auth function is most likely currently just your dbAuth config. First, we need to add the following import statement:
import { OAuthHandler } from '@spoonjoy/redwoodjs-dbauth-oauth-api'
Now, go right to the bottom of the handler
function. You'll see:
return await authHandler.invoke()
Change that to the following:
const oAuthHandler = new OAuthHandler(event, context, authHandler, {
// The name of the property you'd call on `db` to access your OAuth table.
// i.e. if your Prisma model is named `OAuth` this value would be `oAuth`, as in `db.oAuth`
oAuthModelAccessor: 'oAuth',
// You can instead do `enabledProviders: {}`, but I find it more clear to be explicitly not enabling these
// We'll enable at least one of these later on, but leave it like this for now
enabledProviders: { apple: false, github: false, google: false },
})
switch (event.path) {
case '/auth':
return await authHandler.invoke()
case '/auth/oauth':
return await oAuthHandler.invoke()
default:
throw new Error('Unknown auth path')
}
There's a couple things happening in that snippet.
First, we created a new instance of the OAuthHandler - this is a class that's responsible for managing the OAuth authentication process. When you create a new instance of OAuthHandler, you pass in several arguments:
event
: This represents the incoming HTTP request. It contains information about the request, such as the URL path (event.path), HTTP method, headers, and body.- This is one of the parameters to the function
handler
- This is one of the parameters to the function
context
: This provides information about the runtime context in which the function is executing.- This is one of the parameters to the function
handler
- This is one of the parameters to the function
authHandler
: This is an instance of the DbAuthHandler class, which handles traditional authentication. The OAuthHandler uses this to reuse some of its functionality and configurations.- You might have given this variable a different name
config
object: This contains configuration settings for the OAuthHandler. For example,oAuthModelAccessor
specifies the property name to access the OAuth table in the database, andenabledProviders
specifies which OAuth providers are enabled.
The OAuthHandler
provides a method called invoke()
, which is called every time there's an OAuth related request. It manages the communication with the OAuth provider, handles the OAuth process (like getting the authorization code, exchanging it for a token, and saving the token), and sends the appropriate response back to the client.
Then, we switched the original return statement to a switch statement. This is done to handle different types of authentication requests - it checks the event.path property, which represents the URL path of the incoming HTTP request. Depending on the URL path, it will execute different code:
- If the URL path is
/auth
, it will executeauthHandler.invoke()
. This is the original behavior, which handles traditional authentication requests. - If the URL path is
/auth/oauth
, it will executeoAuthHandler.invoke()
. This is new behavior introduced to handle OAuth requests. In other words, the switch statement allows the application to handle both traditional and OAuth authentication requests, routing them to the appropriate handler based on the URL path.
Updating the api auth
library
Double check that your getCurrentUser
method is selecting the user's email
attribute, if it exists on your user
model. For example, if you're using a field called username
as your username, you might need to make a change that looks like this:
export const getCurrentUser = async (
session: Decoded
): Promise<ICurrentUser> => {
if (!session || typeof session.id !== 'string') {
throw new Error('Invalid session')
}
const user = await db.user.findUnique({
where: { id: session.id },
select: {
id: true,
username: true,
+ email: true
},
})
if (!user) throw new AuthenticationError('You are not logged in.')
else return user
}
This is optional, but helpful as another way to associate linked accounts with your users.
All done!
And that's it! If you haven't yet set up the web
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)!