Updated March 8, 2016
Google OAuth using Meteor and React Native
Originally publish on medium.com.
As technology changes articles get out of date. This post may be in that state now so be aware that things may not work exactly the same way. If you want the latest information please sign up for my email list.
Allowing a user to sign up for your app via OAuth has become common place. If you’ve used Meteor in the past you’ve experienced the ease through which you can set up OAuth in the browser or in Cordova. If you haven’t, or you’re just interested in learning more about the flexibility, I would suggest checking out this recipe by The Meteor Chef. It may also help you configure Google OAuth if you run into any issues throughout this post.
One problem with the way Meteor handles the integrated OAuth is its reliance on the browser. This one took me a while to workout but I think I’ve now find a solution I’m happy with — which we’ll be exploring today. So, let’s jump into how to set up Google OAuth for your React Native application that is backed by Meteor.
First, clone the Meteor React Native boilerplate. You can do so via
git clone [https://github.com/spencercarli/react-native-meteor-boilerplate.git](https://github.com/spencercarli/react-native-meteor-boilerplate.git)
Setting up the Google App
First thing we have to do is set up the Google App so that we can authorize our Meteor app, and later our React Native app, with Google. This is a step you have to take regardless of the platform you’re using.
- Go to the Google Developer Console
- Create a new google project (image 01 and 02)
3. Open API Manager
4. Click the Credentials Tab
5. Set a Product Name
6. Create an OAuth Client ID
7. Add Settings for the Client ID
8. Save your New Credentials
Setting up the Meteor Server
We’re going to have very basic Google login on the web side of things. This is just to show that everything is working and the same login system is used for both the Meteor web and the React Native app.
- Add the necessary Meteor packages
meteor add accounts-google service-configuration accounts-ui
2. Create the following directories:
MeteorApp/client, MeteorApp/server
3. Delete the following files (just cleaning things up)
MeteorApp.html, MeteorApp.js, MeteorApp.css
4. Now we need to create a new client file
MeteorApp/client/index.html
<head>
<title>MeteorApp</title>
</head>
<body>
<h1>Welcome to Meteor!</h1>
{{> loginButtons}}
</body>
5. Now create a .gitignore file in the MeteorApp directory. We don’t want to commit any private keys
MeteorApp/.gitignore
settings.json
6. Create a settings file to store our google credentials
MeteorApp/settings.json
{
"google": {
"clientId": "YOUR_APP_CLIENT_ID",
"secret": "YOUR_APP_SECRET"
}
}
7. Now we actually have to configure our Google OAuth services so the package has access to the proper data.
MeteorApp/server/google-oauth.js
const settings = Meteor.settings.google;
if (settings) {
ServiceConfiguration.configurations.remove({
service: 'google',
});
ServiceConfiguration.configurations.insert({
service: 'google',
clientId: settings.clientId,
secret: settings.secret,
});
}
8. Start your Meteor server with your settings file
meteor — settings settings.json
9. Visit localhost:3000 and try to login to your app via Google! If you followed the steps above everything should be good to go and you can move on to the next section.
Setup the React Native App
We’re going to rely on the react-native-google-signin package which I have used in the past. It’s got a shared API for both iOS and Android which makes setting up our application code simple.
Code in this section will all happen in the RNApp/ directory so make sure you run any commands from there.
- Save the Google SignIn package to your node modules
npm install react-native-google-signin — save
2. Configure your platform(s) I tried to write detailed steps for both iOS and Android for this section but I realized that it would make this post incredibly long and that I would just be repeating the documentation of the package itself so rather than try to reinvent the wheel I will push you towards the proper resources instead.
For iOS: https://github.com/apptailor/react-native-google-signin/blob/master/ios-guide.md
For Android: https://github.com/apptailor/react-native-google-signin/blob/master/android-guide.md
Note: Depending on what you’re experience is/where your app is at you may have to sign your Android app before following the instructions from react-native-google-signin. You can access those instructions here here
3. (IOS ONLY) Add the CLIENT_ID from the .plist file you downloaded earlier to your app config file like so:
config.js
let opts = {
env: 'dev', // ['dev', 'staging', 'prod']
// codePushDeploymentKey: '',
ddpConfig: {
maintainCollections: true,
},
google: {
iosClientId: 'XYZ_YOUR_CLIENT_ID.apps.googleusercontent.com',
},
};
4. Now we actually have to configure Google Sign In for our app. We’re going to update the componentWillMount function in our main file. First we need to import our config file and GoogleSignin library.
RNApp/app/index.js
import config from './config';
import { GoogleSignin } from 'react-native-google-signin';
/*
Removed for brevity
*/
componentWillMount() {
GoogleSignin.configure({
iosClientId: config.google.iosClientId,
});
ddpClient.connect((error, wasReconnect) => {
if (error) {
this.setState({connected: false});
} else {
this.setState({connected: true});
ddpClient.loginWithToken((err, res) => {
if (!err) this.handleSignedInStatus(true);
});
}
});
}
5. Next we have to set up our sign in screen. Just to make it easy for this tutorial we’re going to go ahead and replace our entire signIn container component.
Sigin.js
import React, { Component, StyleSheet, Text, View } from 'react-native';
import Button from '../components/button';
import ddpClient from '../ddp';
export default class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
};
}
render() {
let button;
if (this.props.connected) {
button = <Button text="Sign In with Google" />;
}
return (
<View style={styles.container}>
<Text style={styles.main}>Sign In Screen</Text>
<Text style={styles.error}>{this.state.error}</Text>
{button}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
main: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginHorizontal: 20,
marginVertical: 5,
padding: 5,
},
buttons: {
flexDirection: 'row',
},
error: {
color: 'red',
height: 20,
},
});
6. Now that we got rid of the old, unnecessary, code let’s make it do something.
RNApp/app/containers/signIn.js
import { GoogleSignin } from 'react-native-google-signin';
/*
Removed for brevity
*/
handleGoogleSignIn() {
GoogleSignin.signIn()
.then((user) => {
console.log(user);
})
.catch((err) => {
this.setState({error: err.message});
})
.done();
}
7. Setup an onPress handler for our button
RNApp/app/containers/signIn.js
button = (
<Button
text="Sign In with Google"
onPress={this.handleGoogleSignIn.bind(this)}
/>
);
You should now be able to authenticate with Google! If you’re actively building along with this tutorial you should see your user information returned in the console. Let’s actually make it do something though.
8. Create a function that changes our logged in state when we login
RNApp/app/containers/signIn.js
handleDDPSignIn(googleUser) {
if (googleUser) {
this.props.changedSignedIn(true);
}
}
/*
Removed for brevity
*/
handleGoogleSignIn() {
GoogleSignin.signIn()
.then((user) => {
this.handleDDPSignIn(user); // Change this line
})
.catch((err) => {
this.setState({error: err.message});
})
.done();
}
9. We don’t want the user to have to enter their credentials each time they use the app so we should automatically log them in when they open the app, if they’ve previously authenticated.
RNApp/app/containers/signIn.js
componentWillReceiveProps(nextProps) {
if (nextProps) {
GoogleSignin.currentUserAsync()
.then((user) => {
this.handleDDPSignIn(user)
})
.done();
}
}
10. Once a user is logged in we need to give them the ability to log out. We’re going to modify one function to do so in our signOut file.
RNApp/app/containers/signOut.js
import { GoogleSignin } from 'react-native-google-signin';
/*
Removed for brevity
*/
handleSignOut() {
// ddpClient.logout(() => {
// this.props.changedSignedIn(false);
// });
GoogleSignin.signOut()
.then(() => {
console.log('google sign out');
this.props.changedSignedIn(false);
})
.catch((err) => {
console.log('google sign out error', err);
});
So we’ve now got a system that “fakes” logging in. Once they authenticate with Google we change the screen they’re viewing but they’re not actually logged in yet. That’s what we’ll handle now.
Connecting the Meteor and React Native Apps
- First thing we have to do is switch over to the MeteorApp/ directory in our terminal. Then run the command
meteor add spencercarli:accounts-google-oauth
This is a package that allows us to use native Google SDK’s for logging in and hooks us into the normal Meteor accounts system. Feel free to check out the source code.
- Next thing we need is a list of all the Google Client IDs associated with our app. You can find these at the Google Developer Console. Navigate to the API Manager and then to the Credentials tab.
- Once you’ve got all of your Client IDs we need to update our settings file.
MeteorApp/settings.json
{
"google": {
"client_secret": "MY_WEB_APP_SECRET",
"client_id": "MY_WEB_APP_CLIENT_ID",
"validClientIds": [
"MY_WEB_APP_CLIENT_ID",
"MY_IOS_APP_CLIENT_ID"
]
}
}
The Client IDs will go into the validClientIds array.
3. Still working in the Meteor app we need to tell our Google Service Configuration about these valid client IDs. Make sure to name the key exactly as I do because that is how the package is accessing them.
MeteorApp/server/google-oauth.js
const settings = Meteor.settings.google;
if (settings) {
ServiceConfiguration.configurations.remove({
service: 'google',
});
ServiceConfiguration.configurations.insert({
service: 'google',
clientId: settings.clientId,
secret: settings.secret,
validClientIds: Meteor.settings.google.validClientIds,
});
}
The Meteor server should now be all good to go! You may want to restart your server.
meteor —-settings settings.json
4. Switching over to the React Native app we have just three things to do before we can ship it. First is to add a function to login with Google. We’ll keep this with our other ddp logic.
RNApp/app/ddp.js
ddpClient.loginWithGoogle = (user, cb) => {
let params = { google: user };
return ddpClient.call('login', [params], (err, res) => {
ddpClient.onAuthResponse(err, res);
cb && cb(err, res);
});
};
5. Next let’s log the user in after they authenticate with Google.
RNApp/app/containers/signIn.js
handleDDPSignIn(googleUser) {
if (googleUser) {
ddpClient.loginWithGoogle(googleUser, (err, res) => {
if (err) {
this.setState({error: err.reason});
} else {
this.props.changedSignedIn(true);
}
});
}
}
6. Lastly we need to log the user out of our server as well when they press log out.
RNApp/app/containers/signOut.js
handleSignOut() {
GoogleSignin.signOut()
.then(() => {
ddpClient.logout(() => {
this.props.changedSignedIn(false);
});
})
.catch((err) => {
console.log('google sign out error', err);
});
}
Okay you should now be good to go! This was a long one and a tough one for me to write — really had to dive into the internals of Meteor. If you want to find out how to configure offline server access for your app please check out the React Native Google Signin docs. The Meteor package is already setup to accept the data.
You can find an example of what we walked through here. It’s missing some data (such as client tokens) but should at least show you where everything is setup and give a full picture of the steps above.
I hope you found this helpful. I find OAuth tricky to setup and even trickier to explain. It would be immensely valuable to me if you could give me feedback on this article and let me know what you liked and what could be improved. You can reach me on Twitter (@spencer_carli) or open an issue on the Github repo.
Originally published at blog.differential.com on March 9, 2016.