Changelog April 7, 2023

Category
Company
Published

Expo 48 support, Improving our components, Runtime keys for Next.js...

This week, the team has been improving the DX of Clerk and introducing some important features.

Expo 48 Support

Clerk now has full support for Expo 48 with this change we introduced a brand new hook called useOAuth this hook dramatically improves the developer experience. Here are two examples of the code to handle Discord OAuth, both handle account transfers.

Before

import { useSignUp, useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()
  const { signUp } = useSignUp()
  if (!isLoaded) return null

  const handleSignInWithDiscordPress = async () => {
    try {
      const redirectUrl = AuthSession.makeRedirectUri({
        path: '/oauth-native-callback',
      })

      await signIn.create({
        strategy: 'oauth_discord',
        redirectUrl,
      })

      const {
        firstFactorVerification: { externalVerificationRedirectURL },
      } = signIn

      if (!externalVerificationRedirectURL)
        throw 'Something went wrong during the OAuth flow. Try again.'

      const authResult = await AuthSession.startAsync({
        authUrl: externalVerificationRedirectURL.toString(),
        returnUrl: redirectUrl,
      })

      if (authResult.type !== 'success') {
        throw 'Something went wrong during the OAuth flow. Try again.'
      }

      // Get the rotatingTokenNonce from the redirect URL parameters
      const { rotating_token_nonce: rotatingTokenNonce } = authResult.params

      await signIn.reload({ rotatingTokenNonce })

      const { createdSessionId } = signIn

      if (createdSessionId) {
        // If we have a createdSessionId, then auth was successful
        await setSession(createdSessionId)
      } else {
        // If we have no createdSessionId, then this is a first time sign-in, so
        // we should process this as a signUp instead
        // Throw if we're not in the right state for creating a new user
        if (!signUp || signIn.firstFactorVerification.status !== 'transferable') {
          throw 'Something went wrong during the Sign up OAuth flow. Please ensure that all sign up requirements are met.'
        }

        console.log(
          "Didn't have an account transferring, following through with new account sign up",
        )

        // Create user
        await signUp.create({ transfer: true })
        await signUp.reload({
          rotatingTokenNonce: authResult.params.rotating_token_nonce,
        })
        await setSession(signUp.createdSessionId)
      }
    } catch (err) {
      console.log(JSON.stringify(err, null, 2))
      console.log('error signing in', err)
    }
  }

  return (
    <View className="rounded-lg border-2 border-gray-500 p-4">
      <Button title="Sign in with Discord" onPress={handleSignInWithDiscordPress} />
    </View>
  )
}

export default SignInWithOAuth

After

import { useOAuth } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'
import { useWarmUpBrowser } from '../hooks/useWarmUpBrowser'

const SignInWithOAuth = () => {
  useWarmUpBrowser()

  const { startOAuthFlow } = useOAuth({ strategy: 'oauth_discord' })

  const handleSignInWithDiscordPress = React.useCallback(async () => {
    try {
      const { createdSessionId, signIn, signUp, setActive } = await startOAuthFlow()
      if (createdSessionId) {
        setActive({ session: createdSessionId })
      } else {
        // Modify this code to use signIn or signUp to set this missing requirements you set in your dashboard.
        throw new Error('There are unmet requirements, modifiy this else to handle them')
      }
    } catch (err) {
      console.log(JSON.stringify(err, null, 2))
      console.log('error signing in', err)
    }
  }, [])

  return (
    <View className="rounded-lg border-2 border-gray-500 p-4">
      <Button title="Sign in with Discord" onPress={handleSignInWithDiscordPress} />
    </View>
  )
}

export default SignInWithOAuth

At this point we believe that our authentication offering is better than Expo's direct offer! If you are ready to get started with Expo and Clerk, check out our documentation

Improving our components

Many of our users offer email,username and phone as a way for their users to authenticate. In the past we would have a single input for all of the options and the user would have to type in their phone number including the country code which made it difficult

We now offer a way to switch the input allow the user to see a familiar phone input.

@clerk/nextjs runtime key support

We can now support keys at runtime for users who may have multiple projects under a single monorepo or want fine grain usage of keys.

export default withClerkMiddleware(
  (request: NextRequest) => {
    // pass the secret key to getAuth
    const { userId } = getAuth(req, {
      secretKey: 'CLERK_SECRET_KEY',
    })

    if (!userId) {
      // redirect the users to /pages/sign-in/[[...index]].ts

      const signInUrl = new URL('/sign-in', request.url)
      signInUrl.searchParams.set('redirect_url', request.url)
      return NextResponse.redirect(signInUrl)
    }
    return NextResponse.next()
  },
  {
    // pass the keys to Middleware
    secretKey: 'CLERK_SECRET_KEY',
  },
)

Create your organization with a slug

The <CreateOrganization/> component now exposes the slug field for you to input the slug you want when an organization is created. It is still optional and by default will use the name of the organization.

Dutch language support

We now have support for localization in Dutch thanks to a Clerk user who translated our components.

import { ClerkProvider } from '@clerk/nextjs'
import { nlNL } from '@clerk/localizations'
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ClerkProvider localization={nlNL} {...pageProps}>
      <Component {...pageProps} />
    </ClerkProvider>
  )
}

export default MyApp