Adding Authentication to create-t3-turbo with clerk.dev and Expo
A short guide on how to add authentication to your create-t3-turbo project, including Expo and clerk.
Update: 21.02.2024, 21:08 GMT+1
Thanks for all the kind words! Remember that this post is from 2022 and that a lot has been changed on clerks and Expos side. I recommend following the in- and outs of the official starter repository below.
Update: 26.01.2023, 17:39 GMT+1
Clerk.dev has released their own starter repository. Though you can use the linked repo as a starter, the methods below can be applied to other authentication methods.
Intro
While wanting to develop a multiplatform app - which most of you probably are doing as well - I've stumbled upon the wonders of tRPC and its capabilities. Promptly I've found myself using this stack on my coming app, pasto.cloud, which will help you to synchronize your clipboard contents across multiple devices securely and privately (feel free to check it out and join the waitlist 😙).
Unfortunately, I found out much later, that next-auth
is currently not supporting Expo / React Native and that previous attempts / PRs are inactive.
I've fiddled around for some weeks and found something that's working.
Since a few people have asked me how I solved this problem, I decided to write a short article about it. https://github.com/t3-oss/create-t3-turbo/issues/33#issuecomment-1326447017
Busy, like everyone else, this article is written in a short amount of time. Should some constructive critisism or nice words about it come to mind, please let me know.
The Theory
Setting up clerk.dev
clerk.dev is an authentication provider that helps software engineers quickly and easily implement secure authentication and authorization for their applications. It provides an intuitive web interface for developers to quickly configure secure authentication and authorization for their applications, as well as secure integration with popular identity providers such as Google, Microsoft, and Apple. With Clerk.dev, software engineers can quickly and easily add secure authentication and authorization to their applications, allowing them to focus on developing their core product.
Before continuing, I recommend setting up a starter project in Next.js and React-Native to get familiar with the clerk-sdk. Optionally, a good exercise could be adding an OAuth provider like Discord.
The following links should help you out:
Are you done? Good! 💪
Integrating clerk.dev with tRPC
Recently, the clerk.dev team has released a new part of their documentation, which describes on how to use tRPC with clerk.
After setting up your create-t3-turbo
project and having made the adjustments from the clerk docs, you can make authenticated calls from your NextJS application to your tRPC instance.
Since everything regarding NextJS seems self-explanatory, we will focus on the expo/react-native part of the application.
Getting Expo / React Native ready
Firstly, we will create a new component in the apps/src/components
directory and call it SignInWithOAuth.tsx
. The file contents are thankfully borrowed and adjusted from clerk's expo starter repository. 😁
import { useSignIn } from "@clerk/clerk-expo"; import React from "react"; import { Button } from "react-native"; import { log } from "../utils/logger"; import * as AuthSession from "expo-auth-session"; const SignInWithOAuth = () => { const { isLoaded, signIn, setSession } = useSignIn(); if (!isLoaded) return null; const handleSignInPress = async () => { try { const redirectUrl = AuthSession.makeRedirectUri({ path: "/oauth-native-callback", }); await signIn.create({ strategy: "oauth_discord", redirectUrl, }); const { firstFactorVerification: { externalVerificationRedirectURL }, } = signIn; const result = await AuthSession.startAsync({ authUrl: externalVerificationRedirectURL!.toString(), returnUrl: redirectUrl, }); //@ts-ignore const { type, params } = result || {}; // Check all the possible AuthSession results // https://docs.expo.dev/versions/latest/sdk/auth-session/#returns-7 if (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 } = params; await signIn.reload({ rotatingTokenNonce }); const { createdSessionId } = signIn; if (!createdSessionId) { throw "Something went wrong during the Sign in OAuth flow. Please ensure that all sign in requirements are met."; } await setSession(createdSessionId); return; } catch (err) { // @ts-ignore log("Error:> " + (err.errors ? err.errors[0].message : err)); } }; return <Button title="Sign in with Discord" onPress={handleSignInPress} />; }; export default SignInWithOAuth;
Naturally, being consistent with using the Discord OAuth provider, adjustments have been made. This component will display a "Sign In"-Button which handles the authorization flows.
NOTE: Be sure to install the corresponding dependencies along the way!
The last key step is creating a new tRPC-Provider handling the JWTs from the clerk.dev service. This is accomplished by wrapping the already implemented TRPCProvider
with an auth-context:
export type TRPCAuthContextProps = { children: React.ReactNode; }; export const TRPCAuthContext: FC<TRPCAuthContextProps> = ({ children }) => { // get JWT from clerk const { getToken } = useAuth(); // minimal example on how to set the token in the provider const [authToken, setAuthToken] = React.useState<string | null>(null); useEffect(() => { getToken().then((token) => { setAuthToken(token); }); }, []); if (!authToken) return <Text>Loading</Text>; return <TRPCProvider authToken={authToken}>{children}</TRPCProvider>; };
Lastly, we modify the TRPCProvider
by telling it to use an Authorization-Bearer Header
export type TRPCProviderProps = { children: React.ReactNode; authToken: string | null; }; export const TRPCProvider: React.FC<TRPCProviderProps> = ({ children, authToken, }) => { const [queryClient] = React.useState(() => new QueryClient()); const [trpcClient] = React.useState(() => trpc.createClient({ transformer, links: [ httpBatchLink({ headers() { return { Authorization: authToken || "", }; }, url: `${getBaseUrl()}/api/trpc`, }), ], }) ); return ( <trpc.Provider client={trpcClient} queryClient={queryClient}> <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> </trpc.Provider> ); };
That's it! Your app is capable of using authenticated tRPC on mobile, as well as web. :)
Questions? Comments? Compliments?
Get in touch with me - valerius.me