DEV Community

Pedro Alcântara
Pedro Alcântara

Posted on

How i implemented my server login screen for Mastodon

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 and PugText 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("");
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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",
  }
);
Enter fullscreen mode Exit fullscreen mode

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 });
      }
    });
  }}
/>
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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)