Skip to content

Commit

Permalink
RSC: Add 'use client' to auth templates (#10766)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobbe committed Jun 9, 2024
1 parent 3f8102b commit 0512b45
Show file tree
Hide file tree
Showing 13 changed files with 369 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client'

import { Auth0Client } from '@auth0/auth0-spa-js'

import { createAuth } from '@redwoodjs/auth-auth0-web'

const auth0 = new Auth0Client({
domain: process.env.AUTH0_DOMAIN || '',
clientId: process.env.AUTH0_CLIENT_ID || '',
authorizationParams: {
redirect_uri: process.env.AUTH0_REDIRECT_URI,
audience: process.env.AUTH0_AUDIENCE,
},

// Storing tokens in the browser's local storage provides persistence across page refreshes and browser tabs.
// But if an attacker can run JavaScript in your SPA using a cross-site scripting (XSS) attack,
// they can retrieve the tokens stored in local storage.
// See https://auth0.com/docs/libraries/auth0-spa-js#change-storage-options.
cacheLocation: 'localstorage',

// `useRefreshTokens` is required for automatically extending sessions beyond what's set in the initial JWT expiration.
// See https://auth0.com/docs/tokens/refresh-tokens.
// useRefreshTokens: true,
})

export const { AuthProvider, useAuth } = createAuth(auth0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client'

import { PublicClientApplication } from '@azure/msal-browser'

import { createAuth } from '@redwoodjs/auth-azure-active-directory-web'

const azureActiveDirectoryClient = new PublicClientApplication({
auth: {
clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID || '',
authority: process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY,
redirectUri: process.env.AZURE_ACTIVE_DIRECTORY_REDIRECT_URI,
postLogoutRedirectUri:
process.env.AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI,
},
})

export const { AuthProvider, useAuth } = createAuth(azureActiveDirectoryClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use client'

import React, { useEffect } from 'react'

import { ClerkProvider, useUser } from '@clerk/clerk-react'

import { createAuth } from '@redwoodjs/auth-clerk-web'
import { navigate } from '@redwoodjs/router'

export const { AuthProvider: ClerkRwAuthProvider, useAuth } = createAuth()

const ClerkStatusUpdater = () => {
const { isSignedIn, user, isLoaded } = useUser()
const { reauthenticate } = useAuth()

useEffect(() => {
if (isLoaded) {
reauthenticate()
}
}, [isSignedIn, user, reauthenticate, isLoaded])

return null
}

type ClerkOptions =
| { publishableKey: string; frontendApi?: never }
| { publishableKey?: never; frontendApi: string }

interface Props {
children: React.ReactNode
}

const ClerkProviderWrapper = ({
children,
clerkOptions,
}: Props & { clerkOptions: ClerkOptions }) => {
const { reauthenticate } = useAuth()

return (
<ClerkProvider
{...clerkOptions}
navigate={(to) => reauthenticate().then(() => navigate(to))}
>
{children}
<ClerkStatusUpdater />
</ClerkProvider>
)
}

export const AuthProvider = ({ children }: Props) => {
const publishableKey = process.env.CLERK_PUBLISHABLE_KEY
const frontendApi =
process.env.CLERK_FRONTEND_API_URL || process.env.CLERK_FRONTEND_API

const clerkOptions: ClerkOptions = publishableKey
? { publishableKey }
: { frontendApi }

return (
<ClerkRwAuthProvider>
<ClerkProviderWrapper clerkOptions={clerkOptions}>
{children}
</ClerkProviderWrapper>
</ClerkRwAuthProvider>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use client'

import { createAuthentication } from '@redwoodjs/auth'

// If you're integrating with an auth service provider you should delete this interface.
// Instead you should import the type from their auth client sdk.
export interface AuthClient {
login: () => User
logout: () => void
signup: () => User
getToken: () => string
getUserMetadata: () => User | null
}

// If you're integrating with an auth service provider you should delete this interface.
// This type should be inferred from the general interface above.
interface User {
// The name of the id variable will vary depending on what auth service
// provider you're integrating with. Another common name is `sub`
id: string
email?: string
username?: string
roles: string[]
}

// If you're integrating with an auth service provider you should delete this interface
// This type should be inferred from the general interface above
export interface ValidateResetTokenResponse {
error?: string
[key: string]: string | undefined
}

// Replace this with the auth service provider client sdk
const client = {
login: () => ({
id: 'unique-user-id',
email: 'email@example.com',
roles: [],
}),
signup: () => ({
id: 'unique-user-id',
email: 'email@example.com',
roles: [],
}),
logout: () => {},
getToken: () => 'super-secret-short-lived-token',
getUserMetadata: () => ({
id: 'unique-user-id',
email: 'email@example.com',
roles: [],
}),
}

function createAuth() {
const authImplementation = createAuthImplementation(client)

// You can pass custom provider hooks here if you need to as a second
// argument. See the Redwood framework source code for how that's used
return createAuthentication(authImplementation)
}

// This is where most of the integration work will take place. You should keep
// the shape of this object (i.e. keep all the key names) but change all the
// values/functions to use methods from the auth service provider client sdk
// you're integrating with
function createAuthImplementation(client: AuthClient) {
return {
type: 'custom-auth',
client,
login: async () => client.login(),
logout: async () => client.logout(),
signup: async () => client.signup(),
getToken: async () => client.getToken(),
/**
* Actual user metadata might look something like this
* {
* "id": "11111111-2222-3333-4444-5555555555555",
* "aud": "authenticated",
* "role": "authenticated",
* "roles": ["admin"],
* "email": "email@example.com",
* "app_metadata": {
* "provider": "email"
* },
* "user_metadata": null,
* "created_at": "2016-05-15T19:53:12.368652374-07:00",
* "updated_at": "2016-05-15T19:53:12.368652374-07:00"
* }
*/
getUserMetadata: async () => client.getUserMetadata(),
}
}

export const { AuthProvider, useAuth } = createAuth()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client'

import { createDbAuthClient, createAuth } from '@redwoodjs/auth-dbauth-web'

const dbAuthClient = createDbAuthClient()

export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client'

import { createDbAuthClient, createAuth } from '@redwoodjs/auth-dbauth-web'
import WebAuthnClient from '@redwoodjs/auth-dbauth-web/webAuthn'

const dbAuthClient = createDbAuthClient({ webAuthn: new WebAuthnClient() })

export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client'

import { initializeApp, getApp, getApps } from 'firebase/app'
import * as firebaseAuth from 'firebase/auth'

import { createAuth } from '@redwoodjs/auth-firebase-web'

const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,

// Optional config, may be needed, depending on how you use firebase
// projectId: process.env.FIREBASE_PROJECT_ID,
// storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
// messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
// appId: process.env.FIREBASE_APP_ID,
}

const firebaseApp = ((config) => {
const apps = getApps()

if (!apps.length) {
initializeApp(config)
}

return getApp()
})(firebaseConfig)

export const firebaseClient = {
firebaseAuth,
firebaseApp, // optional
}

export const { AuthProvider, useAuth } = createAuth(firebaseClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client'

import netlifyIdentity from 'netlify-identity-widget'

import { createAuth } from '@redwoodjs/auth-netlify-web'
import { isBrowser } from '@redwoodjs/prerender/browserUtils'

isBrowser && netlifyIdentity.init()

export const { AuthProvider, useAuth } = createAuth(netlifyIdentity)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use client'

import { createClient } from '@supabase/supabase-js'

import { createAuth } from '@redwoodjs/auth-supabase-web'

const supabaseClient = createClient(
process.env.SUPABASE_URL || '',
process.env.SUPABASE_KEY || ''
)

export const { AuthProvider, useAuth } = createAuth(supabaseClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client'

import SuperTokens, { SuperTokensWrapper } from 'supertokens-auth-react'
import Session from 'supertokens-auth-react/recipe/session'
import ThirdPartyEmailPassword, {
Github,
Google,
Apple,
} from 'supertokens-auth-react/recipe/thirdpartyemailpassword'
import { ThirdPartyEmailPasswordPreBuiltUI } from 'supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui'

import { createAuth } from '@redwoodjs/auth-supertokens-web'
import { isBrowser } from '@redwoodjs/prerender/browserUtils'

const websiteDomain =
process.env.SUPERTOKENS_WEBSITE_DOMAIN || 'http://localhost:8910'
const apiDomain = process.env.SUPERTOKENS_API_DOMAIN || websiteDomain
const apiGatewayPath =
process.env.SUPERTOKENS_API_GATEWAY_PATH || '/.redwood/functions'

const superTokensClient = {
sessionRecipe: Session,
redirectToAuth: SuperTokens.redirectToAuth,
}

export const PreBuiltUI = [ThirdPartyEmailPasswordPreBuiltUI]

isBrowser &&
SuperTokens.init({
appInfo: {
appName: process.env.SUPERTOKENS_APP_NAME,
apiDomain,
websiteDomain,
apiGatewayPath,
websiteBasePath: '/auth',
apiBasePath: '/auth',
},
recipeList: [
Session.init(),
ThirdPartyEmailPassword.init({
signInAndUpFeature: {
providers: [Github.init(), Google.init(), Apple.init()],
},
}),
],
})

const { AuthProvider: SuperTokensAuthProvider, useAuth } =
createAuth(superTokensClient)

interface Props {
children: React.ReactNode
}

const AuthProvider = ({ children }: Props) => {
return (
<SuperTokensWrapper>
<SuperTokensAuthProvider>{children}</SuperTokensAuthProvider>
</SuperTokensWrapper>
)
}

export { AuthProvider, useAuth }
12 changes: 11 additions & 1 deletion packages/cli-helpers/src/auth/__tests__/authTasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import fs from 'fs'
import path from 'path'

import { vol } from 'memfs'
import { vi, beforeEach, describe, it, expect, test } from 'vitest'
import { vi, afterAll, beforeEach, describe, it, expect, test } from 'vitest'

import { getPaths } from '../../lib/paths.js'
import { isTypeScriptProject } from '../../lib/project.js'
Expand Down Expand Up @@ -92,6 +92,12 @@ function platformPath(filePath: string) {
return filePath.split('/').join(path.sep)
}

const original_RWJS_CWD = process.env.RWJS_CWD

afterAll(() => {
process.env.RWJS_CWD = original_RWJS_CWD
})

beforeEach(() => {
vi.restoreAllMocks()
vi.mocked(isTypeScriptProject).mockReturnValue(true)
Expand All @@ -100,7 +106,10 @@ beforeEach(() => {
mockedPathGenerator('App.tsx', 'Routes.tsx'),
)

process.env.RWJS_CWD = getPaths().base

vol.fromJSON({
[path.join(getPaths().base, 'redwood.toml')]: '# redwood.toml',
[path.join(
getPaths().base,
platformPath('/templates/web/auth.ts.template'),
Expand Down Expand Up @@ -152,6 +161,7 @@ describe('authTasks', () => {
// though it was on the mock filesystem.
vol.reset()
vol.fromJSON({
[path.join(getPaths().base, 'redwood.toml')]: '# redwood.toml',
[getPaths().web.app]: webAppTsx,
[getPaths().api.graphql]: graphqlTs,
[getPaths().web.routes]: routesTsx,
Expand Down
Loading

0 comments on commit 0512b45

Please sign in to comment.