In our Pugdom project, we recently implemented a server login screen for Mastodon integration using React Native and Expo. This article will walk through the key techniques and approaches we used to set up and authenticate a user with their Mastodon instance, allowing for a smooth, efficient user experience.
Overview
The goal of this screen is to allow users to input their Mastodon server URL, authenticate via OAuth, and then navigate to the home screen, where they can interact with their Mastodon account.
Key Libraries and Tools
- React Native: Core framework for mobile app development.
- Expo AuthSession: For handling the OAuth 2.0 flow.
- AsyncStorage: For persisting data, such as user authentication and server information.
- React Navigation: For handling navigation between screens.
-
Custom components: Like
PugButton
andPugText
for consistent UI elements.
Core Techniques
1. Managing the Server URL Input
The screen starts by allowing the user to input the Mastodon server URL they wish to connect to. We use a simple state to capture the server URL from the user input.
const [server, setServer] = useState("");
const [serverUrl, setServerUrl] = useState("");
We also perform basic validation and URL formatting to ensure the user’s input is in the correct format:
const handleSave = useCallback(async () => {
let tempServer = server.trim();
if (!tempServer.startsWith("http://") && !tempServer.startsWith("https://")) {
tempServer = `https://${tempServer}`;
}
setServerUrl(tempServer);
await AsyncStorage.setItem("serverUrl", tempServer);
}, [server]);
This step ensures that regardless of what the user inputs, it is stored as a valid URL format in AsyncStorage for future use.
2. OAuth Authentication with Expo AuthSession
For handling the OAuth flow, we use AuthSession
from Expo, which simplifies the authentication process by abstracting a lot of the complexity of OAuth 2.0.
We initialize the OAuth request like this:
const [request, response, promptAsync] = AuthSession.useAuthRequest(
{
clientId: config.CLIENT_ID,
redirectUri: AuthSession.makeRedirectUri({ scheme: "pugdom" }),
scopes: ["read", "write", "follow"],
usePKCE: false,
responseType: AuthSession.ResponseType.Code,
},
{
authorizationEndpoint: serverUrl + "/oauth/authorize",
}
);
We prompt the user to authenticate when they press the "Sign in" button, triggering the OAuth flow:
<PugButton
title="Sign in"
onPress={async () => {
await handleSave().then(() => {
if (serverUrl) {
promptAsync({ showInRecents: true });
}
});
}}
/>
3. Handling Authentication Responses and Storing User Data
Once the OAuth process is complete, the app captures the authorization code returned from the server:
useEffect(() => {
if (serverUrl && response?.type === "success" && response.params.code) {
const code = response.params.code;
(async () => {
const accessToken = await getToken(serverUrl, code);
const userInfo = await getUserInfo(serverUrl, accessToken);
if (userInfo?.username) {
const fullUserInfo = { ...userInfo, accessToken, serverUrl };
await AsyncStorage.setItem("userInfo", JSON.stringify(fullUserInfo));
setAppParam("username", userInfo.username);
navigation.navigate("Home", { username: userInfo.username });
}
})();
}
}, [response]);
The app exchanges the authorization code for an access token, then retrieves the user’s information, which is stored in both AsyncStorage and our global app context for easy access throughout the app.
4. Persisting User Authentication
To enhance the user experience, we check if the user is already authenticated when the screen loads. If they are, we skip the login process and navigate directly to the home screen:
useEffect(() => {
const checkUserAuthentication = async () => {
const userInfo = await AsyncStorage.getItem("userInfo");
if (userInfo) {
const parsedUserInfo = JSON.parse(userInfo);
navigation.replace("Home", { username: parsedUserInfo.username });
} else {
setLoading(false);
}
};
checkUserAuthentication();
}, [navigation]);
5. UX with Activity Indicators
During authentication and API calls, the app shows a loading spinner to ensure users are aware of ongoing background processes, improving the user experience:
if (loading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
);
}
Conclusion
The server screen in Pugdom provides a seamless way for users to connect to their Mastodon instance using OAuth 2.0. By combining React Native, Expo, and custom components, we’ve created an efficient flow that handles both user authentication and persistence across sessions.
If you have any questions or would like to learn more about implementing similar features, feel free to reach out in the comments below!
Top comments (0)