我正在尝试为博览会实现Firebase电话身份验证。我已经在互联网上关注了许多资源,但是成功了。您能否让我知道是否可能/可用?如有可能,请为博览会分享一些有用的资源
感谢您的期待。
答案 0 :(得分:2)
我遇到了同样的问题,但是找到了解决方案。因此,它是如何工作的:
我们有一个特殊的静态“ Captcha”网页,托管在Domain上,并已在我们的Firebase项目中授权。它仅显示firebase.auth.RecaptchaVerifier
。用户解析验证码,并从回调响应中提供token
字符串。
在应用程序登录屏幕上,我们显示带有{Captcha“页面的WebBrowser
,并通过Linking
方法监听URL更改事件。在新网址上,我们从中提取令牌字符串。
然后,我们使用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组件。
代码示例如下所示:
df.pivot(index='date_stamp', columns='fruit', values='fruit_count').plot()