Back
Mobile | Mon Aug 07 2023

Sign In with Github

Hey friend!

In this project, we will learn how to integrate 'Sign In with GitHub' and Firebase Authentication using React Native Expo

Good to know

  • OAuth (Open Authorization) is an open standard for access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords
  • Access Token is a credential that can be used by an application to access an API, often representing a user's permission to access their data with a specific application, and typically used in the context of OAuth authentication
  • Scopes refer to the permissions that an application is requesting from a user.

Setup GitHub OAuth app

  • Go to github.com/settings/developers and create a new OAuth App

  • Add your app scheme as authorized callback URL as the following example:

    codewithbeto://

Note that my app scheme is codewithbeto, but we need to add :// for it to work as a URL

Once you create your OAuth App on GitHub, copy the Client ID and Client Secret, If you don't see a client secret key, you may need to generate one

Setup Firebase

  • Create a new Firebase project
  • Enable Authentication and select GitHub as provider
  • You will see a modal asking for your Client ID and Client Secret. Go ahead and paste your credentials
  • Save changes and that's it

Linking to your app

To link to your development build or standalone app, you need to specify a custom URL scheme for your app. You can register a scheme in your app config app.json, by adding a string under the scheme key:

Learn more about linking to your app on the Expo Docs

{
  "expo": {
    "scheme": "codewithbeto",
  }
}

Dependencies

These are the essential dependencies required for this to function. Simply copy and paste them into your terminal, then press enter to install them all simultaneously 👍

npx expo install firebase
npx expo install expo-web-browser
npx expo install expo-auth-session expo-crypto
npx expo install @react-native-async-storage/async-storage

We'll use AuthSession from Expo because is the easiest way to add web browser based authentication to our App, you can learn more about expo-auth-session here

Environment variables

It's a good idea to have environment variables so that we don't end up exposing our credentials in this project we use Expo SDK 49 which now supports .env variables out of the box

If you are using lower versions you would need to use another alternative

Your .env file should look something like this:

EXPO_PUBLIC_GITHUB_CLIENT_ID = xxxxxxxxxxxxxxxxxxx
EXPO_PUBLIC_GITHUB_CLIENT_SECRET = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 
EXPO_PUBLIC_FIREBASE_API_KEY = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EXPO_PUBLIC_FIREBASE_PROJECT_ID = xxxxxxxxxxxxxxxx
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID = xxxxxxxxxxxxx
EXPO_PUBLIC_FIREBASE_APP_ID = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Remember that this is optional and if you don't want to have env variables you can just use your credentials directly

Connecting our app to Firebase

This should be pretty straight forward, you just need to create a file that will hold your backend credentials and initialize the app

In my case my file name is firebaseConfig.ts and looks like this:

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
 
const firebaseConfig = {
  apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID,
};
 
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);

You can find these credentials on Firebase within your project settings

If you are stuck at some point, you can always checkout the video ☝️

Integrating sign in screen

At this point we are ready to start working with the authentication

I'm assuming that you've already set up your authentication flow, but if you haven't already check the video!

If you want to learn more about authentication flows I highly recommend check out my React Native Course, we have one complete section dedicated to navigation

Let's start with the UI, we need a button so this is how mine looks:

<Icon.Button
  name="github"
  color={currentTheme === "dark" ? "white" : "black"}
  backgroundColor="transparent"
  size={30}
  onPress={() => {
    promptAsync({ windowName: "Code with Beto" });
  }}
>
  <Text style={{ fontSize: 17, fontWeight: "500" }}>Sign In with Github</Text>
</Icon.Button>

For now, let's ignore the promptAsync function, I'll explain that in a minute

Like I mentioned before expo-auth-session helps us abstract a lot of complexity when dealing with browser-based authentication, it comes with a hook called useAuthRequest that basically does everything for us 😅

useAuthRequest will load an authorization request for a code. When the user accepts to share their info with our app the method completes then the response will be fulfilled

This is how useAuthRequest looks like:

const [request, response, promptAsync] = useAuthRequest({ ... }, { ... });

As you can see, it returns three things,

  • request: An instance of AuthRequest that can be used to prompt the user for authorization
  • response: This is null until promptAsync has been invoked. Once fulfilled it will return information about the authorization.
  • promptAsync: When invoked, a web browser will open up and prompt the user for authentication

useAuthRequest also receives two parameters config and discovery

  • config: This is basically an object that specifies what provider to use, in our case GitHub
  • discovery: Another object with enpoints to use for authenticating the user. This is only required when requesting a code (which is what we want in this case)

Now that we have a pretty good idea of what useAuthRequest does let's see how it would look with our configuration:

// GitHub Endpoints
const discovery = {
  authorizationEndpoint: "https://github.com/login/oauth/authorize",
  tokenEndpoint: "https://github.com/login/oauth/access_token",
  revocationEndpoint: `https://github.com/settings/connections/applications/${process.env.EXPO_PUBLIC_GITHUB_CLIENT_ID}`,
};
 
const [request, response, promptAsync] = useAuthRequest(
  {
    clientId: process.env.EXPO_PUBLIC_GITHUB_CLIENT_ID!,
    scopes: ["identity", "user:email", "user:follow"],
    redirectUri: makeRedirectUri(),
  },
  discovery,
);

This is how it looks, useAuthRequest will use the discovery endpoints to authenticate the user with GitHub

After a successful app authentication, GitHub provides a temporary code value. We'll need to POST this code back to GitHub with our client secret in exchange for an access_token

In the scopes section we are asking permissions for what info we want to access from the user. You can learn more about GitHub available scopes here

The redirectUri is the link to redirect back to our app once the user has granted permissions, makeRedirectUri() will use the scheme property of our app.json config

If you remember, when we created our UI we added the promptAsync() to our button and if you try to press it now, everything should work (more or less)

Handling authentication response

At this point after the user authenticates we should be able to simply grab the code from the response.params

Now we need to use this code to get the user access_token

Let's create a function to handle this response

// Imports
import { GithubAuthProvider, signInWithCredential } from "firebase/auth";
import { makeRedirectUri, useAuthRequest } from "expo-auth-session";
 
// This goes inside your component
// This useEffect will run the handleResponse function when response changes
React.useEffect(() => {
  handleResponse();
}, [response]);
 
async function handleResponse() {
  // Verify that everything went well
  if (response?.type === "success") {
    // Here we grab the code from the response
    const { code } = response.params;
 
    // And use this code to get the access_token
    const { token_type, scope, access_token } = await createTokenWithCode(code);
 
    // Just in case we don't have the token return early
    if (!access_token) return;
 
    // GithubAuthProvider is a class that we can import from 'firebase/auth'
    // We pass the token and it returns a credential
    const credential = GithubAuthProvider.credential(access_token);
 
    // Finally we use that credential to register the user in Firebase
    const data = await signInWithCredential(auth, credential);
  }
}
 
// This function makes a POST request for the token
async function createTokenWithCode(code: string) {
  const url =
    `https://github.com/login/oauth/access_token` +
    `?client_id=${process.env.EXPO_PUBLIC_GITHUB_CLIENT_ID}` +
    `&client_secret=${process.env.EXPO_PUBLIC_GITHUB_CLIENT_SECRET}` +
    `&code=${code}`; // 👈 we are passing the code here
 
  const res = await fetch(url, {
    method: "POST"
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
  });
 
  // The response should come with: { token_type, scope, access_token }
  return res.json();
}

That's it 🎉

After this you should see the user registered on the Firebase console

Do you want to learn to integrate other providers like Apple, Facebook, Google, Email and more? Check the React Native Course

Youtube GitHubDownload

Go back to Projects