- Expo : React Native framework
- Clerk : Authentication provider
- MongoDB Realm with Device Sync : Offline-first database provider
For MongoDB and Firebase Auth, refer this other repo
- Create free cluster
- Create an app under App Services
- Enable Device Sync.
- Also enable
Developer Mode
since we want to create schemas programmatically instead of defining it in Atlas
- Also enable
- Under
Authentication
, enableEmail and Password
- This step may not be necessary but do this if you want to do a sanity check
- Let's automatically verify the users
- Now, for our
Reset
password, enter a dummy site such aslocalhost
. Otherwise, we cannot save the changes.
- Deploy the App.
- Create an Expo app with Realm.
- May need to do some
prebuild
but there are some good resources available from MongoDB docs - Define a simple schema:
Task
- Wrap our entry point with
RealmProvider
to test it offline by creating some sample tasks - Now, wrap with
AppProvider
andUserProvider
. - Let's create a simple
LogIn
page to test Atlas Sync by itself. This will be the temporaryfallback
component forUserProvider
- We will be using random, dummy emails and passwords since they are automatically verified anyway.
- If everything is done correctly, a user with a random email and password will be reflected in
Pending
upon register and underUsers
upon logging in in your Atlas Users dashboard.
Now, we are sure that our Expo and Atlas App Services are set up correctly.
- Set up Clerk normally:
- Create
Log in
,Register
components. I will be ignoringReset
password - Wrap the application with
ClerkProvider
. Now, ClerkProvider will be the outermost provider.
- Create
- Usually, with Clerk, we will have
export default function RootLayout() {
// ...
return (
<ClerkProvider publishableKey={CLERK_KEY} tokenCache={cache}>
<Navigation />
</ClerkProvider>
)
}
where Navigation
checks whether a user is signed in.
Define our fallback
component of UserProvider
to be ClerkLogin
, and replace Navigation
component with TaskListScreen
.
Usually done this way when using Realm. This is not right because the moment we navigate to another page, such as Register
from our Login page, we may get the error:
Attempted to navigate before mounting the Root Layout
I assume this is because UserProvider
immediately loads the ClerkLogin
instead of mounting the root component, such as Navigation
, as above. You can verify this behaviour by directing to a different sign in page through Navigation
and a different fallback
component. You will be routed to the fallback
component
Create an InitialLayout
which acts as Navigation
. In this component, we check if:
- user is logged into Realm through
useApp
- user is signed in via Clerk
Both conditions satisfy -> redirect to TaskListScreen. Else, redirect to Login
page.
Then in our RootLayout
,
export default function RootLayout() {
// ...
return (
<ClerkProvider publishableKey={CLERK_KEY} tokenCache={cache}>
<AppProvider id={APP_ID}>
<UserProvider fallback={InitialLayout}>
<RealmProvider schema={[Task]}>
<InitialLayout />
</RealmProvider>
</UserProvider>
</AppProvider>
</ClerkProvider>
)
}
We set fallback
to be exactly InitalLayout
. In other words, instead of letting Realm handle routing based on auth status, we just let Clerk handle it in root component.
Now, we are ready to work with Custom JWT Authentication
in Atlas App services.
- In the Atlas App service, use JWK URI as the verification method
- Set the
Audience
field to the endpoint found on Clerk for example:https://your-app-123.clerk.accounts.dev/
- Now, in Clerk, create a custom JWT template containing all the metadata that you need, such as the email, username. More importantly, include an
aud
field:
Example:
{
"id": "{{user.id}}",
"aud": "https://quick-mite-52.accounts.dev/",
"name": "{{user.username}}",
"role": "authenticated",
"email": "{{user.primary_email_address}}"
}
And that's it, now whenever you register through Clerk, a new user will be reflected on your Atlas App.