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://
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
andClient 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 authorizationresponse
: This is null untilpromptAsync
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 GitHubdiscovery
: Another object with enpoints to use for authenticating the user. This is only required when requesting acode
(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
Links
Go back to Projects