博览会支持Firebase电话身份验证吗?

时间:2018-07-06 22:23:07

标签: firebase react-native firebase-authentication expo

我正在尝试为博览会实现Firebase电话身份验证。我已经在互联网上关注了许多资源,但是成功了。您能否让我知道是否可能/可用?如有可能,请为博览会分享一些有用的资源

感谢您的期待。

6 个答案:

答案 0 :(得分:2)

我遇到了同样的问题,但是找到了解决方案。因此,它是如何工作的:

  1. 我们有一个特殊的静态“ Captcha”网页,托管在Domain上,并已在我们的Firebase项目中授权。它仅显示firebase.auth.RecaptchaVerifier。用户解析验证码,并从回调响应中提供token字符串。

  2. 在应用程序登录屏幕上,我们显示带有{Captcha“页面的WebBrowser,并通过Linking方法监听URL更改事件。在新网址上,我们从中提取令牌字符串。

  3. 然后,我们使用firebase.auth.ApplicationVerifier创建伪造的token对象,并将其传递给firebase.auth().signInWithPhoneNumber(带有电话号码)。短信代码将被发送。

我在下面编写了经过测试的最简单的代码。您可以直接“复制粘贴”它。只需添加firebase配置(两个配置都必须相同)并设置正确的“ Captcha”页面网址即可。不要忘记电话必须以国际格式输入。在此代码中,“ Captcha”页面托管在Firebase托管上,因此它会通过包含init.js自动初始化并默认为授权。

“验证码”页面(托管在Firebase托管上):

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <title>Entering captcha</title>
</head>
<body>
    <p style="text-align: center; font-size: 1.2em;">Please, enter captcha for continue<p/>

    <script src="/__/firebase/5.3.1/firebase-app.js"></script>
    <script src="/__/firebase/5.3.1/firebase-auth.js"></script>
    <script src="/__/firebase/init.js"></script>
    <script>

        function getToken(callback) {
            var container = document.createElement('div');
            container.id = 'captcha';
            document.body.appendChild(container);
            var captcha = new firebase.auth.RecaptchaVerifier('captcha', {
                'size': 'normal',
                'callback': function(token) {
                    callback(token);
                },
                'expired-callback': function() {
                    callback('');
                }
            });
            captcha.render().then(function() {
                captcha.verify();
            });
        }

        function sendTokenToApp(token) {
            var baseUri = decodeURIComponent(location.search.replace(/^\?appurl\=/, ''));
            location.href = baseUri + '/?token=' + encodeURIComponent(token);
        }

        document.addEventListener('DOMContentLoaded', function() {
            getToken(sendTokenToApp);
        });

    </script>
</body>
</html>

博览会项目中的验证屏幕

import * as React from 'react'
import {Text, View, ScrollView, TextInput, Button} from 'react-native'
import {Linking, WebBrowser} from 'expo'
import firebase from 'firebase/app'
import 'firebase/auth'

const captchaUrl = `https://my-firebase-hosting/captcha-page.html?appurl=${Linking.makeUrl('')}`

firebase.initializeApp({
    //firebase config
});

export default class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            user: undefined,
            phone: '',
            confirmationResult: undefined,
            code: ''
        }
        firebase.auth().onAuthStateChanged(user => {
            this.setState({user})
        })
    }

    onPhoneChange = (phone) => {
        this.setState({phone})
    }
    onPhoneComplete = async () => {
        let token = null
        const listener = ({url}) => {
            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) {
            const {phone} = this.state
            //fake firebase.auth.ApplicationVerifier
            const captchaVerifier = {
                type: 'recaptcha',
                verify: () => Promise.resolve(token)
            }
            try {
                const confirmationResult = await firebase.auth().signInWithPhoneNumber(phone, captchaVerifier)
                this.setState({confirmationResult})
            } catch (e) {
                console.warn(e)
            }

        }
    }
    onCodeChange = (code) => {
        this.setState({code})
    }
    onSignIn = async () => {
        const {confirmationResult, code} = this.state
        try {
            await confirmationResult.confirm(code)
        } catch (e) {
            console.warn(e)
        }
        this.reset()
    }
    onSignOut = async () => {
        try {
            await firebase.auth().signOut()
        } catch (e) {
            console.warn(e)
        }
    }
    reset = () => {
        this.setState({
            phone: '',
            phoneCompleted: false,
            confirmationResult: undefined,
            code: ''
        })
    }

    render() {
        if (this.state.user)
            return (
                <ScrollView style={{padding: 20, marginTop: 20}}>
                    <Text>You signed in</Text>
                    <Button
                        onPress={this.onSignOut}
                        title="Sign out"
                    />
                </ScrollView>
            )

        if (!this.state.confirmationResult)
            return (
                <ScrollView style={{padding: 20, marginTop: 20}}>
                    <TextInput
                        value={this.state.phone}
                        onChangeText={this.onPhoneChange}
                        keyboardType="phone-pad"
                        placeholder="Your phone"
                    />
                    <Button
                        onPress={this.onPhoneComplete}
                        title="Next"
                    />
                </ScrollView>
            )
        else
            return (
                <ScrollView style={{padding: 20, marginTop: 20}}>
                    <TextInput
                        value={this.state.code}
                        onChangeText={this.onCodeChange}
                        keyboardType="numeric"
                        placeholder="Code from SMS"
                    />
                    <Button
                        onPress={this.onSignIn}
                        title="Sign in"
                    />
                </ScrollView>
            )
    }
}

答案 1 :(得分:1)

我使用基于Damian Buchet版本的状态挂钩使功能等同。验证码页面是相同的。 React本机模块:

import React, {useState} from 'react'
import {Text, View, TextInput, Button,StyleSheet, KeyboardAvoidingView} from 'react-native'
import { WebView } from 'react-native-webview';
import firebase from '../../components/utils/firebase'  /contains firebase initiation


const captchaUrl = 'https://my-domain.web.app/captcha-page.html';

const LoginScreenPhone = props => {
const [phoneNumber, setPhoneNumber] = useState();
const [step, setStep] = useState('initial');
const [smsCode, setSmsCode] = useState();
const [verificationId, setVerificationId]=useState();

const onAuthStateChanged = async user => {
    if (user) {
        const token = await firebase.auth().currentUser.getIdToken();
        if (token) {
            // User is fully logged in, with JWT in token variable
        }
    }
}

firebase.auth().onAuthStateChanged(onAuthStateChanged);



const onGetMessage = async event => {

    const message = event.nativeEvent.data;
    console.log(message);
    switch (message) {
        case "DOMLoaded":

            return;
        case "ErrorSmsCode":
            // SMS Not sent or Captcha verification failed. You can do whatever you want here
            return;
        case "":
            return;
        default: {
            setStep('promptSmsCode');
            setVerificationId(message);
            }
        }
}


const onSignIn = async () => {
    setStep('smsCodeSubmitted');
    const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, smsCode);
    firebase.auth().signInWithCredential(credential);
    props.navigation.navigate('Home');
}

return (
    <View>
    {step==='initial' && (
        <KeyboardAvoidingView  behavior="padding" enabled>
            <TextInput
                label='Phone Number'
                value={phoneNumber}
                onChangeText={phone =>setPhoneNumber(phone)}
                mode="outlined"
            />
            <Button mode="contained" onPress={()=>setStep('phoneSubmitted')} title=' Send me the code!'>

            </Button>
        </KeyboardAvoidingView >
    )}

  {step==='phoneSubmitted' && (
        <View style={{flex:1, minHeight:800}}>
            <Text>{`getToken('${phoneNumber}')`}</Text>
        <WebView
         injectedJavaScript={`getToken('${phoneNumber}')`}          
            source={{ uri: captchaUrl }}
            onMessage={onGetMessage}
        />
        </View>
    )}

   {step==='promptSmsCode' && (<KeyboardAvoidingView behavior="padding" enabled>
            <TextInput
                label='Verification code'
                value={smsCode}
                onChangeText={(sms)=>setSmsCode(sms)}
                mode="outlined"
                keyboardType='numeric'
            />
            <Button mode="contained" onPress={onSignIn} title='Send'>

            </Button>
    </KeyboardAvoidingView >)}
    </View>
);
}


export default LoginScreenPhone;

答案 2 :(得分:0)

好吧@Rinat的回答几乎是完美的。

验证码页面中的此功能有问题

function sendTokenToApp(token) {
  var baseUri = decodeURIComponent(location.search.replace(/^\?appurl\=/, ''));
  location.href = baseUri + '/?token=' + encodeURIComponent(token);
}

它可与iOS(Safari)一起使用,但事实证明Chrome不允许

location.href

自定义URL(我们试图将用户重定向到自定义URL,exp://192.12.12.31)

这是新功能:

function sendTokenToApp(token) {
            var baseUri = decodeURIComponent(location.search.replace(/^\?appurl\=/, ''));
            const finalUrl = location.href = baseUri + '/?token=' + encodeURIComponent(token);
            const continueBtn = document.querySelector('#continue-btn');
            continueBtn.onclick = (event)=>{
                window.open(finalUrl,'_blank')
            }
            continueBtn.style.display = "block";
}

当然,您必须在HTML中添加一个按钮,以便您可以单击它。

这是完整的代码:

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <title>Entering captcha</title>
</head>
<body>
    <p style="text-align: center; font-size: 1.2em;">Please, enter captcha for continue<p/>
    <button id="continue-btn" style="display:none">Continue to app</button>

    <script src="/__/firebase/5.3.1/firebase-app.js"></script>
    <script src="/__/firebase/5.3.1/firebase-auth.js"></script>
    <script src="/__/firebase/init.js"></script>
    <script>

        function getToken(callback) {
            var container = document.createElement('div');
            container.id = 'captcha';
            document.body.appendChild(container);
            var captcha = new firebase.auth.RecaptchaVerifier('captcha', {
                'size': 'normal',
                'callback': function(token) {
                    callback(token);
                },
                'expired-callback': function() {
                    callback('');
                }
            });
            captcha.render().then(function() {
                captcha.verify();
            });
        }

        function sendTokenToApp(token) {
            var baseUri = decodeURIComponent(location.search.replace(/^\?appurl\=/, ''));
            const finalUrl = location.href = baseUri + '/?token=' + encodeURIComponent(token);
            const continueBtn = document.querySelector('#continue-btn');
            continueBtn.onclick = (event)=>{
                window.open(finalUrl,'_blank')
            }
            continueBtn.style.display = "block";
        }

        document.addEventListener('DOMContentLoaded', function() {
            getToken(sendTokenToApp);
        });

    </script>
</body>
</html>

我花了将近7个小时才弄清楚了,因此希望对您有所帮助!

制作后编辑:

别忘了将“ scheme”:“ appName” 添加到博览会应用或浏览器的app.json中,因为深链接问题。

阅读

https://docs.expo.io/versions/latest/workflow/linking#in-a-standalone-app

答案 3 :(得分:0)

这是我的解决方案,基于@Rinat的解决方案。 先前代码的主要问题是firebase.auth().signInWithPhoneNumber永远不会触发,因为它不在webView中,并且firebase> 6.3.3需要有效的域进行身份验证。 我决定使用React Native Webview来简化WebView和Native之间的通信。

本机方

import React from 'react'
import { KeyboardAvoidingView  } from 'react-native';
import { TextInput, Button } from 'react-native-paper';
import { WebView } from 'react-native-webview';

import firebase from 'firebase/app';
import 'firebase/auth';

firebase.initializeApp({
    //...your firebase config
});

const captchaUrl = 'https://yourfirebasehosting/captcha.html';

export default class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            phoneNumber: '',
            phoneSubmitted: false,
            promptSmsCode: false,
            smsCode: '',
            smsCodeSubmitted: false
        }
        firebase.auth().onAuthStateChanged(this.onAuthStateChanged);
    }

    onAuthStateChanged = async user => {
        if (user) {
            const token = await firebase.auth().currentUser.getIdToken();
            if (token) {
                // User is fully logged in, with JWT in token variable
            }
        }
    }

    updatePhoneNumber = phoneNumber => this.setState({phoneNumber});
    updateSmsCode = smsCode => this.setState({smsCode});

    onSubmitPhoneNumber = () => this.setState({phoneSubmitted: true});

    onGetMessage = async event => {
        const { phoneNumber } = this.state;
        const message = event.nativeEvent.data;

        switch (message) {
            case "DOMLoaded":
                this.webviewRef.injectJavaScript(`getToken('${phoneNumber}')`);
                return;
            case "ErrorSmsCode":
                // SMS Not sent or Captcha verification failed. You can do whatever you want here
                return;
            case "":
                return;
            default: {
                this.setState({
                    promptSmsCode: true,
                    verificationId: message,
                })
            }
        }
    }

    onSignIn = async () => {
        this.setState({smsCodeSubmitted: true});
        const { smsCode, verificationId } = this.state;
        const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, smsCode);
        firebase.auth().signInWithCredential(credential);
    }

    render() {

        const { phoneSubmitted, phoneNumber, promptSmsCode, smsCode, smsCodeSubmitted } = this.state;

        if (!phoneSubmitted) return (
            <KeyboardAvoidingView style={styles.container} behavior="padding" enabled>
                <TextInput
                    label='Phone Number'
                    value={phoneNumber}
                    onChangeText={this.updatePhoneNumber}
                    mode="outlined"
                />
                <Button mode="contained" onPress={this.onSubmitPhoneNumber}>
                    Send me the code!
                </Button>
            </KeyboardAvoidingView >
        );

        if (!promptSmsCode) return (
            <WebView
                ref={r => (this.webviewRef = r)}
                source={{ uri: captchaUrl }}
                onMessage={this.onGetMessage}
            />
        )

        return (
            <KeyboardAvoidingView style={styles.container} behavior="padding" enabled>
                <TextInput
                    label='Verification code'
                    value={smsCode}
                    onChangeText={this.updateSmsCode}
                    mode="outlined"
                    disabled={smsCodeSubmitted}
                    keyboardType='numeric'
                />
                <Button mode="contained" onPress={this.onSignIn} disabled={smsCodeSubmitted}>
                    Send
                </Button>
            </KeyboardAvoidingView >
        );
    }
}

captcha.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <title>Entering captcha</title>
</head>
<body>
    <script src="/__/firebase/6.3.3/firebase-app.js"></script>
    <script src="/__/firebase/6.3.3/firebase-auth.js"></script>
    <script src="/__/firebase/init.js"></script>
    <script>
        function getToken(phoneNumber) {
            var container = document.createElement('div');
            container.id = 'captcha';
            document.body.appendChild(container);
            window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('captcha', {
                'size': 'normal',
                'callback': function(response) {
                    var appVerifier = window.recaptchaVerifier;
                    firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
                        .then(function (confirmationResult) {
                            window.ReactNativeWebView.postMessage(confirmationResult.verificationId);
                        }).catch(function (error) {
                            window.ReactNativeWebView.postMessage('ErrorSmsCode');
                        });
                }
            });

            window.recaptchaVerifier.render().then(function() {
                window.recaptchaVerifier.verify();
            });
        }

        document.addEventListener('DOMContentLoaded', function() {
            window.ReactNativeWebView.postMessage('DOMLoaded');
        });

    </script>
</body>
</html>

答案 4 :(得分:0)

您好,谢谢您的解决方案..为我工作.. 但是,为了获得更好的方法,我们可以通过添加不可见的验证码验证程序来跳过验证码

// React Native Side

     import * as React from 'react'
    import { View, ScrollView, TextInput, Button, StyleSheet, WebView } from 'react-native';
    import { Text } from "galio-framework";
    import { Linking } from 'expo';
    import * as firebase from 'firebase';
    import OTPInputView from '@twotalltotems/react-native-otp-input'
    import theme from '../constants/Theme';

    const captchaUrl = `your firebase host /index.html?appurl=${Linking.makeUrl('')}`

    firebase.initializeApp({
        //firebase config

    });

    export default class PhoneAUth extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                user: undefined,
                phone: '',
                confirmationResult: undefined,
                code: '',
                isWebView: false
            }
            firebase.auth().onAuthStateChanged(user => {
                this.setState({ user })
            })
        }

    onPhoneChange = (phone) => {
        this.setState({ phone })
    }
    _onNavigationStateChange(webViewState) {
        console.log(webViewState.url)
        this.onPhoneComplete(webViewState.url)
    }
    onPhoneComplete = async (url) => {
        let token = null
        console.log("ok");
        //WebBrowser.dismissBrowser()
        const tokenEncoded = Linking.parse(url).queryParams['token']
        if (tokenEncoded)
            token = decodeURIComponent(tokenEncoded)

        this.verifyCaptchaSendSms(token);


    }
    verifyCaptchaSendSms = async (token) => {
        if (token) {
            const { phone } = this.state
            //fake firebase.auth.ApplicationVerifier
            const captchaVerifier = {
                type: 'recaptcha',
                verify: () => Promise.resolve(token)
            }
            try {
                const confirmationResult = await firebase.auth().signInWithPhoneNumber(phone, captchaVerifier)
                console.log("confirmationResult" + JSON.stringify(confirmationResult));
                this.setState({ confirmationResult, isWebView: false })
            } catch (e) {
                console.warn(e)
            }

        }
    }

    onSignIn = async (code) => {
        const { confirmationResult } = this.state
        try {
            const result = await confirmationResult.confirm(code);
            this.setState({ result });

        } catch (e) {
            console.warn(e)

        }
    }
    onSignOut = async () => {
        try {
            await firebase.auth().signOut()
        } catch (e) {
            console.warn(e)
        }
    }
    reset = () => {
        this.setState({
            phone: '',
            phoneCompleted: false,
            confirmationResult: undefined,
            code: ''
        })
    }

    render() {
    if (this.state.user)
        return (
            <ScrollView style={{padding: 20, marginTop: 20}}>
                <Text>You signed in</Text>
                <Button
                    onPress={this.onSignOut}
                    title="Sign out"
                />
            </ScrollView>
        )
        else if (this.state.isWebView)
            return (
                <WebView
                    ref="webview"
                    source={{ uri: captchaUrl }}
                    onNavigationStateChange={this._onNavigationStateChange.bind(this)}
                    javaScriptEnabled={true}
                    domStorageEnabled={true}
                    injectedJavaScript={this.state.cookie}
                    startInLoadingState={false}
                />

            )
        else if (!this.state.confirmationResult)
            return (
                <ScrollView style={{ padding: 20, marginTop: 20 }}>
                    <TextInput
                        value={this.state.phone}
                        onChangeText={this.onPhoneChange}
                        keyboardType="phone-pad"
                        placeholder="Your phone"
                    />
                    <Button
                        onPress={this.onPhoneComplete}
                        title="Next"
                    />
                </ScrollView>
            )
        else
            return (
                <ScrollView style={{padding: 20, marginTop: 20}}>
                    <TextInput
                        value={this.state.code}
                        onChangeText={this.onCodeChange}
                        keyboardType="numeric"
                        placeholder="Code from SMS"
                    />
                    <Button
                        onPress={this.onSignIn}
                        title="Sign in"
                    />
                </ScrollView>
            )
    }
}
const styles = StyleSheet.create({
    borderStyleBase: {
        width: 30,
        height: 45
    },

    borderStyleHighLighted: {
        borderColor: theme.COLORS.PRIMARY,
    },

    underlineStyleBase: {
        width: 30,
        height: 45,
        borderWidth: 0,
        borderBottomWidth: 1,
    },

    underlineStyleHighLighted: {
        borderColor: theme.COLORS.PRIMARY,
    },
});

// Captcha Side。我使用Firebase Hosting托管了该文件

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Firebase Phone Authentication</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

    <script src="https://www.gstatic.com/firebasejs/4.3.1/firebase.js"></script>
    <script>
        // Your web app's Firebase configuration
        var firebaseConfig = {
    // config
        };
        // Initialize Firebase
        firebase.initializeApp(firebaseConfig);
    </script>
    <script src="https://cdn.firebase.com/libs/firebaseui/2.3.0/firebaseui.js"></script>
    <link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/2.3.0/firebaseui.css" />
    <link href="style.css" rel="stylesheet" type="text/css" media="screen" />

</head>

<body>
    <script>

        function getToken(callback) {
            var container = document.createElement('div');
            container.id = 'captcha';
            document.body.appendChild(container);
            var captcha = new firebase.auth.RecaptchaVerifier('captcha', {

        /****************
         I N V I S I B L E  
        **********************/
                'size': 'invisible',
                'callback': function (token) {
                    callback(token);
                },
                'expired-callback': function () {
                    callback('');
                }
            });
            captcha.render().then(function () {
                captcha.verify();
            });
        }

        function sendTokenToApp(token) {
            var baseUri = decodeURIComponent(location.search.replace(/^\?appurl\=/, ''));
            location.href = 'http://www.google.com + '/?token=' + encodeURIComponent(token);
        }

        document.addEventListener('DOMContentLoaded', function () {
            getToken(sendTokenToApp);
        });

    </script>
    <h2>Verification Code is Sending !! </h2>
    <h3>Please Wait !!</h3>

</body>

</html>

答案 5 :(得分:0)

Doorman 为Expo应用程序提供Firebase电话身份验证支持,而不会分离。它还带有针对Expo / React Native的可自定义UI组件。

链接:https://doorman.cool

代码示例如下所示:

df.pivot(index='date_stamp', columns='fruit', values='fruit_count').plot()