In my previous post, The Complete Guide to User Authentication with the Amplify Framework, I walked through how to add username / password based authentication as well as OAuth with Facebook, Google or Amazon.
In this tutorial, I will be covering mobile authentication using React Native and AWS Amplify. This guide will cover both React Native and Expo. I will cover how to implement the following use cases:
- OAuth with Google & Facebook
- OAuth with Apple
- Hosted UI (Google + Apple + Facebook + Username & Password in one UI)
- Username & password authentication
- Protected routes
- Listening to authentication events
- Basic authentication with the
withAuthenticator
HOC
Getting Started
AWS Amplify provides Authentication APIs and building blocks for developers who want to create apps with real-world production-ready user authentication.
With Amplify you can incorporate username / password based authentication as well as OAuth with Facebook, Google, Amazon, or any third party OAuth provider such as Auth0 or Okta via OIDC.
We also provide a pre-built “Hosted UI” that provides a full OAuth + username / password flow with a single function call.
Introduction to Amazon Cognito
The Amplify Framework uses Amazon Cognito as the main authentication provider. Amazon Cognito User is a managed user directory service that handles user registration, authentication, account recovery & other operations.
Amplify interfaces with Cognito to store user data, including federation with other OpenID providers like Facebook, and Google.
The Amplify CLI automates the access control policies for these AWS resources as well as provides fine grained access controls via GraphQL for protecting data in your APIs.
Most modern applications require multiple authentication options, i.e. Facebook login + Username / password login. Amazon Cognito makes this process easy by allowing you to use a single user registry to authenticate users across multiple authentication types.
In this post, you'll learn how to add authentication to your application using both OAuth as well as username & password login.
OAuth with Apple, Google, & Facebook
Installing the Amplify CLI
To build authentication into your application with Amplify you first need to install the AWS Amplify CLI. The Amplify CLI is a command line tool that allows you to create & deploy various AWS services.
To install the CLI, we'll run the following command:
$ npm install -g @aws-amplify/cli
Next, we'll configure the CLI with a user from our AWS account:
$ amplify configure
For a video walkthrough of the process of configuring the CLI, click here.
Creating the React Native project
Next, we'll create the React Native application we'll be working with.
If using Expo
$ npx expo init rnamplify
> Choose a template: blank
$ cd rnamplify
$ npm install aws-amplify aws-amplify-react-native
If using the React Native CLI
$ npx react-native init rnamplify
$ cd rnamplify
$ npm install aws-amplify aws-amplify-react-native amazon-cognito-identity-js
$ cd ios
$ pod install --repo-update
$ cd ..
Creating the Amplify project
Now we can now initialize a new Amplify project from within the root of our React Native application:
$ amplify init
Here we'll be guided through a series of steps:
- Enter a name for the project: amplifyauth (or your preferred project name)
- Enter a name for the environment: local (or your preferred environment name)
- Choose your default editor: Visual Studio Code (or your text editor)
- Choose the type of app that you're building: javascript
- What javascript framework are you using: react-native
- Source Directory Path: /
- Distribution Directory Path: build
- Build Command: npm run-script build
- Start Command: npm run-script start
- Do you want to use an AWS profile? Y
- Please choose the profile you want to use: YOUR_USER_PROFILE
Now, our Amplify project has been created & we can move on to the next steps.
Creating our App IDs
In our app we'll be having four types of authentication:
- Facebook (OAuth)
- Google (OAuth)
- Apple (OAuth)
- Cognito (username + password)
Next we'll need to create Apple, Facebook, & Google Apps in order to get an App ID & App Secret for each of them. For each provider that you'd like to enable, create App IDs by following the following instructions.
To see instructions for the Facebook setup click here.
To see instructions for the Google setup click here.
To see instructions for the Apple setup see the tutorial here. You only need to create the App ID, the Services ID, and the Private Key. You do not need to create a Client Secret. For the Services ID web domain and Return URL, leave it blank for now.
After you've created the Apple, Facebook, & Google OAuth credentials move on to the next step.
Creating & configuring the authentication service
Now that our Amplify project has been initialized & we have our App IDs & secrets from Apple, Facebook & Google we can add the authentication service.
As of this blog post, the Amplify CLI has not yet added support for Apple so we will need to do that in a separate step by enabling Apple directly in the dashboard. For now, we will only enable Google and Facebook from the CLI.
To add the authentication service, we can run the following command:
$ amplify add auth
# If you already have a project configured & want to now add Social login, run amplify update auth instead
This will walk you through a series of steps:
- Do you want to use the default authentication and security configuration? Default configuration with Social Provider (Federation)
- How do you want users to be able to sign in when using your Cognito User Pool? Username
- What attributes are required for signing up? Email
- What domain name prefix you want us to create for you? amplifyauthXXXXXXXXX (use default or create custom prefix)
- Enter your redirect signin URI: If you are using Expo: exp://127.0.0.1:19000/--/, if you are using the React Native CLI: myapp:// (this can be updated later for production environments)
- Do you want to add another redirect signin URI: N
- Enter your redirect signout URI: If you are using Expo: exp://127.0.0.1:19000/--/, if you are using the React Native CLI: myapp://
- Do you want to add another redirect signout URI: N
- Select the social providers you want to configure for your user pool: Choose Facebook & Google
In the above step we chose Default configuration with Social Provider (Federation). This will allow a combination of Username / Password signin with OAuth. If you only want Username / Password, you could choose Default configuration or Manual Configuration.
We also set the redirect
URI. This is important and we will be updating the Expo or Xcode and Android Studio projects later on with this URI.
Finally, you'll be prompted for your App IDs & Secrets for both Facebook & Google, enter them & press enter to continue.
Now that the authentication service has successfully been configured, we can deploy the service by running the following command:
$ amplify push
After running amplify push
you should see a success message & the OAuth Endpoint should also be logged out to the console:
The OAuth endpoint should look something like this:
https://amplifyauth8e79c995-8e79c995-local.auth.eu-central-1.amazoncognito.com/
This OAuth endpoint is also available for reference in src/aws-exports.js if you need it at any point under the oauth
-> domain
key.
You will need to use this endpoint to finish configuring your Apple, Facebook, & Google OAuth providers.
Configuring Facebook
Next, open the Facebook app we created earlier & click on Basic in the left hand menu.
Scroll to the book & click Add Platform, then choose Website:
For the _Site URL), input the OAuth Endpoint URL with /oauth2/idpresponse
appended into Site URL:
Save changes.
Next, type your OAuth Endpoint into App Domains:
Save changes.
Next, from the navigation bar choose Products and then Set up from Facebook Login & choose Web.
For the Valid OAuth Redirect URIs use the OAuth Endpoint + /oauth2/idpresponse
. If you're prompted for the site URL, also use this endpoint (i.e. https://amplifyauth8e79c995-8e79c995-local.auth.eu-central-1.amazoncognito.com/oauth2/idpresponse):
Save changes.
Make sure your app is Live by clicking the On switch at the top of the page.
Configuring Google
Now that Facebook has been configured we can now configure Google. To do so, let's go to the Google Developer Console & update our OAuth client.
Click on the client ID to update the settings.
Under Authorized JavaScript origins, add the OAuth Endpoint.
For the Authorized redirect URIs, add the OAuth Endpoint with /oauth2/idpresponse
appended to the URL:
Save changes.
Configuring Apple
Open the Apple developer console, click on Certificates, IDs, & Profiles in the left hand menu, then click on Identifiers.
In the App IDs dropdown menu, choose Service IDs.
Click on the Service ID you created earlier, then click Configure next to Sign In with Apple.
Here, enter the Domain and Return URLs. The Domain should be the oauth
domain value located in the aws-exports.js file. The Return URL will be a variation of the domain
that will look like this:
https://<domain>/oauth2/idpresponse
So, your return url could look something like:
https://rnauth6508c5d5-6508c5d5-dev.auth.us-east-1.amazoncognito.com/oauth2/idpresponse
NOTE You do not have to verify the domain because the verification is only required for a transaction method that Amazon Cognito does not use.
Adding Apple Sign In to the Cognito Service
The Amplify CLI integrated the Google and Facebook OAuth services with Amazon Cognito, but to enable Sign In with Apple we must go into the console and do it manually until the Amplify CLI adds this feature. To open the Cognito project, run the following command:
$ amplify console auth
? Which console: User Pool
In the left hand menu, choose Identity Providers. In this section, click on Sign in with Apple and enter the Apple Services ID, the Team ID, the Key ID, and upload the Private Key given to you from the Apple Developer Console.
Next, click on App client settings and enable Sign in with Apple for each app client.
Finally, click on Attribute mapping and be sure to enable email and map it to email.
Configuring local redirect URIs
In the amplify configuration step we set redirect URIs for the app to open back up after the user has been authenticated. Now, we need to enable these redirect URIs on our mobile project. These steps will differ depending on whether you are building with Expo or with the React Native CLI.
Expo - redirect URIs
If you are using expo, open the app.json file and add the following key value pair to the "expo" property:
{
"expo": {
"scheme": "myapp",
// other values
}
}
React Native CLI - redirect URIs
If you're using the React Native CLI and working with the native projects, you will need to configure both the Xcode project as well as the Android Studio project.
iOS - Xcode configuration
For iOS, open the Xcode project (in the ios folder, rnamplify.xcworkspace). Here, open info.plist
as source code, and add the following:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
<dict>
</array>
For a full example, click here.
Android - Android Studio configuration
In Android Studio, open android/app/main/AndroidManifest.xml. In this file, add the following intent-filter:
<intent-filter android:label="filter_react_native">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
For a full example, click here.
Trying it out
Now, the project and the services are configured and we can start writing some JavaScript.
The first thing we need to do is configure the React Native Project to use the Amplify credentials. To do so, open index.js and add the following code:
import Amplify from 'aws-amplify'
import config from './aws-exports'
Amplify.configure(config)
Now, we can test out the authentication APIs. To do so, we will be using the Auth.federatedSignIn
methods. These methods allow us to launch either Federated Sign In with a single provider, or launch the Hosted UI for signing in with any provider.
// import the Auth class
import { Auth } from 'aws-amplify'
<Button
title="Sign in with Google"
onPress={() => Auth.federatedSignIn({ provider: "Google" })}
/>
<Button
title="Sign in with Facebook"
onPress={() => Auth.federatedSignIn({ provider: "Facebook" })}
/>
<Button
title="Sign in with Apple"
onPress={() => Auth.federatedSignIn({ provider: "SignInWithApple" })}
/>
<Button
title="Launch Hosted UI"
onPress={() => Auth.federatedSignIn()}
/>
Next, run the app to test everything out:
# If using Expo
$ expo start
# If not using Expo
$ npx react-native run-ios
# or
$ npx react-native run-android
After signing in, we can test out a couple of other things.
To log out the current user's credentials:
const user = await Auth.currentAuthenticatedUser().catch(err => console.log(err))
Using the above method, we can also check if the current user is signed in at any time. If they are not signed in, the error message will tell us that no user is signed in.
If they are signed in, the user
object will be populated with all of the metadata of the signed in user.
To sign out the current user:
await Auth.signOut()
Username + Password authentication
With our current setup, we can also sign up and sign out users with a username and password.
To do so, we need to capture their info in a form. Using the Auth
class, we can handle many difference scenarios, including but not limited to:
- Signing up
- Confirming sign up (MFA)
- Signing in
- Confirming sign in (MFA)
- Resetting password
Let's take a look how to sign a user up. This is a very basic example that does not take into account switching form state between sign up, sign in, and confirming sign up or sign in. A more detailed example is linked below.
// import the Auth component
import { Auth } from 'aws-amplify'
// store the form state
state = {
username: '', email: '', password: ''
}
// sign the user up
async signUp = () => {
const { username, email, password } = this.state
await Auth.signUp({ username, password, attributes: { email }})
console.log('user successfully signed up')
}
To view all of the methods available on the Auth
class, check out the documentation here.
If you're interested in how to create a custom authentication flow, check out the components in this example, or just check out the entire React Native Authentication Starter here.
Protected / Private Routes
When creating a custom authentication flow, the one thing you need to deal with is protected or private routes.
Protected routes are routes or views that you do not want accessible to certain users. In our example, we will implement protected routes to redirect users who are not signed in and allow users who are signed in to proceed.
The example I will show is assuming you are using React Navigation, but if you are using a different navigation library the idea will still be the same.
Essentially what you need to do is have a listener for a route change, or if you are using React Navigation you can hook directly into the route change using the onNavigationStateChange
prop.
In this function, we can get the user's credentials and check if the user is signed in. If they are signed in, we allow then to continue to the next route.
If they are not signed in, we redirect them to the Authentication screen:
import React from 'react'
import { createSwitchNavigator, createAppContainer, NavigationActions } from 'react-navigation'
import Auth from './nav/auth/Auth'
import MainNav from './nav/main/MainNav'
import { Auth as AmplifyAuth } from 'aws-amplify'
const SwitchNav = createSwitchNavigator({
Auth: {
screen: Auth
},
MainNav: {
screen: MainNav
}
})
const Nav = createAppContainer(SwitchNav)
class App extends React.Component {
checkAuth = async () => {
try {
await AmplifyAuth.currentAuthenticatedUser()
} catch (err) {
this.navigator.dispatch(
NavigationActions.navigate({ routeName: 'Auth' })
)
}
}
render() {
return (
<Nav
ref={nav => this.navigator = nav}
onNavigationStateChange={this.checkAuth}
/>
)
}
}
export default App
For a complete implementation, check out the example project here.
Listening to authentication events
There is a listener we can initialize that will listen to changes in our authentication state and allow us to have access to the type of authentication event that happened and update the application state based on that data.
With Amplify, the Hub
module allows us to do this pretty easily:
import { Hub } from 'aws-amplify';
Hub.listen('auth', (data) => {
const { payload } = data;
console.log('A new auth event has happened: ', data.payload.data.username + ' has ' + data.payload.event);
})
Basic authentication with the withAuthenticator
HOC
If you're just looking to get up and running with basic username + password authentication, you can use the withAuthenticator
HOC.
This component will put authentication in front of any component in your app with just a couple of lines of code:
import { withAuthenticator } from "aws-amplify-react-native";
class App extends React.Component { /* your code /* }
export default withAuthenticator(App)
Top comments (41)
Great article!
I've come across one problem that I just can't seem to resolve with Facebook login via FederatedIdentity - on my iOS simulator everything works great but when I run in the expo app (or push to TestFlight) the redirect back to the app from Facebook throws an error (attached). I'm not sure why I'm seeing a difference between the two. Any thoughts?
Thanks,
Steve.
Hey Steven, I'm sorry you're having that issue. Any way you can open an issue here and then we can escalate it or try to reproduce it / find a resolution?
Done: Expo federated login redirect error #4800
(Thanks for the quick response)
Hi Nader,
Did you have any luck with this? I can't seem to solve it on the device at all :'-(
Thanks,
Steve.
Hi Nader,
I had used your article to setup a Facebook login for a mobile app and it has been working like a charm. Thanks for the detailed setup instructions.
Recently I got an alert from facebook that the facebook app that I created for social login is non complaint with URL policy as it is returning non compliant code. I had followed above instructions originally and checked once the latest amplify instructions at docs.amplify.aws/lib/auth/social/q....
If you refer to the instructions in article above for facebook configuration. It says....
"For the _Site URL), input the OAuth Endpoint URL with /oauth2/idpresponse appended into Site URL:"
This setup seems to be cause for the compliance issue. If I ping
https://<your-user-pool-domain>/oauth2/idpresponse
(using facebook's debug tool - developers.facebook.com/tools/debug), I get a response code 400. But if I ping
https://<your-user-pool-domain>
I get 200.I am not sure if my production app will break if I change the url to just pool-domain without the extension
/oauth2/idpresponse
Any pointers that you could suggest to meet facebook compliance.
I keep coming back to this article, because it seems like the only one on the Internet that covers this topic in detail. That's the sign of a good post :)
Scenario: I'm using expo on an iPhone. I've implemented Sign in with Apple to the point now, where I can click on a button and I get swung out to appleid.apple.com page in Safari.
This opens an action menu for apple ID the first go round. And I click continue. Subsequent logins don't prompt with the action menu, as AppleID is already in use for the expo app.
I then get prompted to open Expo, because of the redirect (bad UX, but hey, I can't see a better way yet).
Now, I'm redirected to the page where I was last in my expo app.
If I use a React useEffect hook to run Auth.currentAuthenticatedUser() on page load, I get "not authenticated" as a response, after returning from appleId auth page.
If I shake the phone and reload the app, I become authenticated by my app (i.e. Auth.currentAuthenticatedUser() works just fine.
Question: I feel like I'm missing a crucial step here. What is it? :)
I figured this out a while back... In short I was being a bit dumb...
The useEffect hook was worthless, because when the user is returned from to the app after successful authorization, they are not immediately recognized as being signed in, so an
Auth.currentAuthenticatedUser()
will throw an error.One problem I had (still have) is it takes about 3 seconds after returning from the Auth provider to for the
data.payload.event
,signIn
, to be "heard" by the hub. This delay only seems to occur on a mobile.Anyhow, to provide better UX, I return the user to a page that has a, "Continue with" button, that sends them to a loading page until the
signIn
event occurs and I can trigger the app into action or handle an error from that point on.Still way too many clicks and popping in/out of windows, which I'm thinking about how to reduce.
Please I need to help me with the following issue:
I get current credentials using
Auth.currentUserCredentials()
after callingAuth.currentAuthenticatedUser()
and it refreshes token every time and that token remains valid for one hour and then it expires and gives me this errorNotAuthorizedException: Invalid login token. Token expired
how can I get a new token when it expires, thanks.Thanks @dabit3 for the article, I have a question tho.
Do you need to eject from expo to use this flow?
Because on one of my application I am using AsyncStorage to keep the user token.
But
facebook/react-native
is depprecated in flavor of@react-native-community/async-storage
I just wanted to know your opinion on this 😉
Thanks for spending time to write this. My main question I have after trying this out, is that it came to my surprise that this flow boots you out of your app and into a web-browser.
This is not a great UX and so I was wondering if you or anyone knew how to integrate with AWS User Pools and Federated login using a native solution such as Expo's expo-google-sign-in or expo-facebook components such that the user doesn't get booted out the app into a browser window?
I'm very new to implementing federated sign-in on a native platform so apologies if it's obvious. If it's not possible, it definitely seems like a feature gap that should be considered.
@dabit3 are these deprecated?
Hey, no you can still use these fields directly in the AppSync schema itself, without the GraphQL Transform library, to set authorization rules.
Oh! Thanks. Now I get it.
Great article!
I followed all the steps as defined here and everything works fine. The only issue that I get is about the app name in the sign in with apple sheet. When Apple presents the user with a consent sheet asking whether to hide email or share, the sheet's title is "Create an account for {App name} using your Apple ID". But in my case the sheet shows "Create an account for null using your Apple ID".
dev-to-uploads.s3.amazonaws.com/i/...
Another thing is that it always asks user whether to share or hide email even if the user has previously signed in. According to Apple docs user is only asked this for the first time and the preferred choice is persisted by Apple.
I do not face this issue when I try Apple login in Cognito's web hosted UI login.
My guess here is that Apple does not get my domain or bundle ID when presenting Sign in with Apple.
Can you please help me with this?
Thanks!
Hi Jay!! I am facing the same issue. If you have fixed this issue could you help me to fix the issue. It would be great if you provide some references. TIA!
Something that i came across after using this, I kept getting stuck on parsing callbackurl and the user never actually signing in once redirected back. I found the snippet below that i needed to add to my appdelegate.m, add the import at the top and the - (BOOL).. etc at the bottom
Hi Nader,
Please excuse me if my question sounds trivial to you. I am a just-started React-native developer. I am facing an issue with redirecting to the Google Auth page while I can successfully redirect to Facebook. But I am not much bothered about this issue as I believe it should only be a small mistake somewhere.
My main concern is about the redirect screens that seem to open up on web browser and NOT in the native Google and Facebook Apps. I would expect the Auth redirections to load the relevant pages on the native Apps. Can you please help me with this doubt as to why is it loading the screens over the browser? Wouldn't it be better if the Auth screens are opened up on the native Apps?
And why it matters to me is because a user would need to provide passwords even if he/she is already logged in to Google/Facebook in his/her mobile phone's native Apps. Please suggest! Thanks in advance!