React Native + FirebaseのSNSログイン機能の実装(GoogleとFacebook)
どうも、サトウダイキです。 前回の続きです。React Native + Firebaseのログイン実装について記載してきます。
前回はメールアドレスと電話番号の実装をしました。
今回はFacebookアカウントとGoogleアカウントでのログインについです。
Facebookアカウントでのログイン
FacebookログインはFirebaseの公式サイト通りやっていけば簡単に実装できます。公式サイトも合わせて確認しながら実装してみてください。
Authenticate Using Facebook Login with JavaScript | Firebase
Facebook for Developersの登録
まずは、Facebook for Developers サイトに登録して、アプリのアプリ ID とアプリシークレットを取得します。
Firebaseの設定
続いて、Fireabaseの設定です。
コンソールに移動して [Authentication] セクションを開きます。
[ログイン方法] タブで [Facebook] を有効にし、Facebook から取得した [アプリ ID] と [app secret] を指定します。
次に、Facebook for Developers サイトの [Product Settings] > [Facebook Login] 構成にある Facebook アプリ設定ページで、OAuth リダイレクト URI(my-app-12345.firebaseapp.com/__/auth/handler など)が [OAuth redirect URIs] のうちの 1 つとしてリストされていることを確認します。
パッケージのインストール
ターミナルを立ち上げ、今回必要なパッケージをインストールします。
yarn add expo-facebook
ロジックの実装
ここからは実装です。ロジック部分から作成していきます。
firebase.tsに追記
export const facebookConfig = { ApplicationKey: '-----' }; export const { FacebookAuthProvider } = firebase.auth;
※ApplicationKey: '-----' のところは自身の値を入力してください。
auth.tsに追記
import firebase, { facebookConfig, FacebookAuthProvider, } from '../configs/firebase'; // Facebookでのユーザ登録 export const facebookSignUp = async () => { await Facebook.initializeAsync(facebookConfig.ApplicationKey, '-----'); const { type, token } = await Facebook.logInWithReadPermissionsAsync( facebookConfig.ApplicationKey, { permissions: ['public_profile'], } ).catch(e => { console.log(e); throw e; }); if (type === 'success') { try { const credential = FacebookAuthProvider.credential(token); const user = await firebase.auth().signInWithCredential(credential); return user; } catch (error) { console.log(error); } } return null; };
※ApplicationKey: '-----' のところは自身の値を入力してください。
大きな流れとしては、initializeAsyncでログインして、signInWithCredentialでサインインします。ExpoとFirebse が関数を用意してくれているので非常に助かります。
UIの実装
続いてUI部分の実装です。
SignUpScreen.tsxに追記
import { emailSignUp, phoneSignUp, * facebookSignUp, * } from '../utils/auth'; ---- 中略 ---- const onPressFacebookSignIn = async (): Promise<void> => { const user = await facebookSignUp(); console.log(user); }; ---- 中略 ---- <Button title="Facebook Login" onPress={onPressFacebookSignIn} />
ボタンを押した時に、先ほど作成したfacebookSignUpを呼び出します。
完成
これでFacebookログインは完成です。
Googleアカウントログイン
最後にGoogleアカウントでのログインについてです。こちらはFacebookログインほど簡単にはできません。実装量が多いです。
Google Developに登録
まずはGoogle Developコンソールにログインして、必要な作業をしていきます。
Create Credentialsをクリック
Credentialsを選択。 こちらの方法はFirebaseでは完結せず、Developpers Consoleでプロジェクトを作成し、認証情報を追加する必要があります。 [認証情報]タブ→[認証情報を作成]→[OAuthクライアントID]を選択しクライアントIDを作成します。Androidの場合はパッケージ名を、iOSの場合はバンドルIDをhost.exp.exponentと入力します。
パッケージのインストール
今回の実装に必要はパッケージをインストールします。
yarn add expo-google-app-auth
ロジックの実装
firabase.tsの追記
export const { FacebookAuthProvider, GoogleAuthProvider } = firebase.auth;
auth.tsの追記
import * as Google from 'expo-google-app-auth'; import firebase, { facebookConfig, FacebookAuthProvider, * GoogleAuthProvider,* } from '../configs/firebase'; // Googelでのユーザ登録 const isUserEqual = ( googleUser: Google.LogInResult, firebaseUser: firebase.User ): boolean => { if (firebaseUser) { const { providerData } = firebaseUser; // eslint-disable-next-line no-plusplus for (let i = 0; i < providerData.length; i++) { if ( providerData[i].providerId === firebase.auth.GoogleAuthProvider.PROVIDER_ID && providerData[i].uid === googleUser.getBasicProfile().getId() ) { return true; } } } return false; }; const onSignInGoolge = (googleUser: Google.LogInResult): void => { const unsubscribe = firebase.auth().onAuthStateChanged(firebaseUser => { unsubscribe(); if (!isUserEqual(googleUser, firebaseUser)) { const credential = firebase.auth.GoogleAuthProvider.credential( googleUser.idToken, googleUser.accessToken ); firebase .auth() .signInWithCredential(credential) .then(() => { console.log('success'); }) .catch(({ massage }) => alert('messgage', massage)); } else { console.log('User already signed-in Firebase.'); } }); }; export const goolgeSignUp = async () => { try { const result = await Google.logInAsync({ behavior: 'web', androidClientId: '--------', iosClientId: '--------', scopes: ['profile', 'email'], }); if (result.type === 'success') { onSignInGoolge(result); return result.accessToken; } return { cancelled: true }; } catch (e) { return { error: true }; } };
※ --------の箇所は先ほ取得した自身の値を設定してください。
処理の大きな流れは、Googleアカウントにログインして、成功したらそのままFirebaseに登録しております。
UIの実装
今作成したロジックを画面から呼び出せるようにします。
SignUpScreen.tsxの追記
import { emailSignUp, goolgeSignUp, phoneSignUp, facebookSignUp, } from '../utils/auth'; ---中略 ---- const onPressGoolgeSignUp = async (): Promise<void> => { goolgeSignUp(); }; ---中略 ---- <Button title="GoogleSignUp" onPress={onPressGoolgeSignUp} />
これでGoogleログインも完了です。
無事完了しました。最後に最終的コードを貼っておきます。
最終的なコード
configs/firebase.ts
import firebase from 'firebase'; export const firebaseConfig = { apiKey: '----', authDomain: '----', databaseURL: '----', projectId:'----', storageBucket: '----', messagingSenderId: '----', appId: '----', measurementId: '----', }; export const facebookConfig = { ApplicationKey: '----' }; export const googleConfig = { androidClientId: '----', iosClientId: '----', }; export const { FacebookAuthProvider, GoogleAuthProvider } = firebase.auth; export default firebase;
utils/auth.ts
import * as Facebook from 'expo-facebook'; import { Linking } from 'expo'; import * as WebBrowser from 'expo-web-browser'; import * as Google from 'expo-google-app-auth'; import firebase, { facebookConfig, googleConfig, FacebookAuthProvider, GoogleAuthProvider, } from '../configs/firebase'; // メールでのユーザ登録 export const emailSignUp = (email: string, password: string): void => { firebase .auth() .createUserWithEmailAndPassword(email, password) .then(user => { if (user) { console.log('Success to Signup'); } }) .catch(error => { console.log(error); }); }; // 電話番号で登録 export const phoneSignUp = async ( phoneNumber: string ): Promise<firebase.auth.ConfirmationResult | null> => { const captchaUrl = `https://------/captcha.html?appurl=${Linking.makeUrl( '' )}`; let token: string | null = null; const listener = ({ url }): void => { WebBrowser.dismissBrowser(); const tokenEncoded = Linking.parse(url).queryParams.token; if (tokenEncoded) token = decodeURIComponent(tokenEncoded); }; Linking.addEventListener('url', listener); await WebBrowser.openBrowserAsync(captchaUrl); Linking.removeEventListener('url', listener); if (token) { // fake firebase.auth.ApplicationVerifier const captchaVerifier = { type: 'recaptcha', verify: () => Promise.resolve(token), }; try { const confirmationResult = await firebase .auth() .signInWithPhoneNumber(phoneNumber, captchaVerifier); return confirmationResult; } catch (e) { console.warn(e); return null; } } return null; }; // Facebookでのユーザ登録 export const facebookSignUp = async () => { await Facebook.initializeAsync(facebookConfig.ApplicationKey, '------'); const { type, token } = await Facebook.logInWithReadPermissionsAsync( facebookConfig.ApplicationKey, { permissions: ['public_profile'], } ).catch(e => { console.log(e); throw e; }); if (type === 'success') { try { const credential = FacebookAuthProvider.credential(token); const user = await firebase.auth().signInWithCredential(credential); return user; } catch (error) { console.log(error); } } return null; }; // Googelでのユーザ登録 const isUserEqual = ( googleUser: Google.LogInResult, firebaseUser: firebase.User ): boolean => { if (firebaseUser) { const { providerData } = firebaseUser; // eslint-disable-next-line no-plusplus for (let i = 0; i < providerData.length; i++) { if ( providerData[i].providerId === GoogleAuthProvider.PROVIDER_ID && providerData[i].uid === googleUser.getBasicProfile().getId() ) { return true; } } } return false; }; const onSignInGoolge = (googleUser: Google.LogInResult): void => { const unsubscribe = firebase.auth().onAuthStateChanged(firebaseUser => { unsubscribe(); if (!isUserEqual(googleUser, firebaseUser)) { const credential = GoogleAuthProvider.credential( googleUser.idToken, googleUser.accessToken ); firebase .auth() .signInWithCredential(credential) .then(() => { console.log('success'); }) .catch(e => console.log('e', e)); } else { console.log('User already signed-in Firebase.'); } }); }; export const goolgeSignUp = async () => { try { const result = await Google.logInAsync({ behavior: 'web', androidClientId: googleConfig.androidClientId, iosClientId: googleConfig.iosClientId, scopes: ['profile', 'email'], }); if (result.type === 'success') { onSignInGoolge(result); return result.accessToken; } return { cancelled: true }; } catch (e) { return { error: true }; } }; export default firebase;
screens/SignUpScreen.tsx
import React, { useState } from 'react'; import { View, StyleSheet, TextInput, Button } from 'react-native'; import { NavigationStackProp } from 'react-navigation-stack'; import { emailSignUp, goolgeSignUp, phoneSignUp, facebookSignUp, } from '../utils/auth'; const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 16, }, textInput: { fontSize: 14, color: 'black', width: '100%', borderBottomWidth: 1, borderBottomColor: 'black', margin: 16, }, }); const SignUpScreen: React.FC<{ navigation: NavigationStackProp }> = ({ navigation, }): JSX.Element => { const [email, setEmail] = useState(); const [password, setPassword] = useState(); const [phoneNumber, setPhoneNumber] = useState(''); const [confirmResult, setConfirmResult] = useState(null); const onPressGoolgeSignUp = async (): Promise<void> => { await goolgeSignUp(); }; const onPressPhoneSignUp = async (): Promise<void> => { const result = await phoneSignUp(phoneNumber); if (result) { setConfirmResult(result); navigation.navigate('VerificationCode', { confirmResult: result }); } else { console.log('error'); } }; const onPressFacebookSignIn = async (): Promise<void> => { const user = await facebookSignUp(); console.log(user); }; return ( <View style={styles.container}> <TextInput style={styles.textInput} value={email} onChangeText={(text: string): void => setEmail(text)} editable maxLength={50} placeholder="Email" autoCapitalize="none" keyboardType="email-address" returnKeyType="done" /> <TextInput style={styles.textInput} value={password} onChangeText={(text: string): void => setPassword(text)} editable maxLength={20} placeholder="Password" autoCapitalize="none" secureTextEntry returnKeyType="done" /> <Button title="登録" onPress={(): void => emailSignUp(email, password)} /> <TextInput style={styles.textInput} value={phoneNumber} onChangeText={(text: string): void => setPhoneNumber(text)} editable maxLength={50} placeholder="Phone" autoCapitalize="none" keyboardType="phone-pad" returnKeyType="done" /> <Button title="PhoneSignUp" onPress={onPressPhoneSignUp} /> <Button title="Facebook Login" onPress={onPressFacebookSignIn} /> <Button title="GoogleSignUp" onPress={onPressGoolgeSignUp} /> </View> ); }; export default SignUpScreen;
src/VerificationCodeScreen.tsx
import React, { useState } from 'react'; import { View, Text, StyleSheet, TextInput, Button } from 'react-native'; import { NavigationStackProp } from 'react-navigation-stack'; const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 16, }, textInput: { fontSize: 14, color: 'black', width: '100%', borderBottomWidth: 1, borderBottomColor: 'black', margin: 16, }, }); const VerificationCodeScreen: React.FC<{ navigation: NavigationStackProp }> = ({ navigation, }): JSX.Element => { const [code, setCode] = useState(''); const onPressConfirmCode = (): void => { if (code.length) { const { params = {} } = navigation.state; params.confirmResult .confirm(code) .then(user => { console.log('Code Confirmed!'); }) .catch(error => console.log(`Code Confirm Error: ${error.message}`)); } }; return ( <View style={styles.container}> <Text>Enter verification code below:</Text> <TextInput style={styles.textInput} value={code} onChangeText={(value): void => setCode(value)} editable maxLength={50} placeholder="Code" autoCapitalize="none" keyboardType="numeric" returnKeyType="done" /> <Button title="Confirm Code" color="#841584" onPress={onPressConfirmCode} /> </View> ); }; export default VerificationCodeScreen;
public/captcha.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Welcome to Firebase Hosting</title> <!-- update the version number as needed --> <script defer src="/__/firebase/7.8.0/firebase-app.js"></script> <!-- include only the Firebase features as you need --> <script defer src="/__/firebase/7.8.0/firebase-auth.js"></script> <script defer src="/__/firebase/7.8.0/firebase-database.js"></script> <script defer src="/__/firebase/7.8.0/firebase-messaging.js"></script> <script defer src="/__/firebase/7.8.0/firebase-storage.js"></script> <!-- initialize the SDK after all desired features are loaded --> <script defer src="/__/firebase/init.js"></script> <style media="screen"> body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; } </style> </head> <body> <p>Please, enter captcha for continue<p/> <button id="continue-btn" style="display:none">Continue to app</button> <script> function getToken(callback) { const containerId = 'captcha'; const container = document.createElement('div'); container.id = containerId; document.body.appendChild(container); var captcha = new firebase.auth.RecaptchaVerifier(containerId, { 'size': 'normal', 'callback': function(token) { callback(token); }, 'expired-callback': function() { callback(''); } }); captcha.render().then(function() { captcha.verify(); }); } function sendTokenToApp(token) { const baseUri = decodeURIComponent(location.search.replace(/^\?appurl\=/, '')); const finalUrl = location.href = baseUri + '/?token=' + encodeURIComponent(token); const continueBtn = document.querySelector('#continue-btn'); continueBtn.onclick = function() { window.open(finalUrl, '_blank'); }; continueBtn.style.display = 'block'; } document.addEventListener('DOMContentLoaded', function() { getToken(sendTokenToApp); }); </script> </body> </html>
※ ---- ----の箇所は全て自身の値に書き換えてください。
まとめ
いかがだったでしょうか。複雑な実装も多かったと思いますが、これで一通りのログイン制御はできると思います。ではまた次回。