Я пытаюсь реализовать аутентификацию телефона firebase для expo. Я следил за многими ресурсами в Интернете, но мне это удалось. Не могли бы вы сообщить мне, возможно ли это / доступно? если есть возможность, поделитесь, пожалуйста, некоторыми полезными ресурсами для выставки
спасибо за ожидание.
Ребята, просто пользуйтесь Flutter и будьте счастливы). Это действительно отличный инструмент. Я использую его около полугода, и не было проблем, которые Flutter не может решить.

У меня была такая же проблема, но я нашел решение. Итак, как это работает:
У нас есть специальная статическая веб-страница «Captcha», размещенная в домене, авторизованном в нашем проекте Firebase. Просто показывает firebase.auth.RecaptchaVerifier. Пользователь разрешает капчу, и она выдает строку token из ответа обратного вызова.
На экране входа в приложение мы показываем WebBrowser со страницей «Captcha» и прослушиваем событие изменения URL-адреса методами Linking. На новом url мы извлекаем из него строку токена.
Затем мы создаем поддельный объект firebase.auth.ApplicationVerifier с помощью token и передаем его firebase.auth().signInWithPhoneNumber (с номером телефона). SMS-код будет отправлен.
Ниже я написал проверенный простейший код. Вы можете напрямую "скопировать-вставить" его. Просто добавьте конфигурацию firebase (эта конфигурация должна быть одинаковой для обоих) и установите правильный URL страницы "Captcha". Не забывайте, что номер телефона должен быть указан в международном формате. В этом коде страница «Captcha» размещена на хостинге firebase, поэтому она автоматически инициализируется путем включения init.js и авторизуется по умолчанию.
Страница "Captcha" (размещен на хостинге 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>
)
}
}
Будет ли страница с капчей автоматически закрываться после того, как пользователь выполнит проверку?
@iAviator, да. Captcha вызывает callback или expired-callback, затем перенаправляет страницы, устанавливая location.href, наконец, это изменение URL-адреса обнаруживается функцией прослушивателя (Linking.addEventListener('url', listener))
Хорошо .. также, как можно реализовать невидимую капчу в react native, где я хочу, чтобы пользователь был проверен без какого-либо взаимодействия на экране?
В любом случае модальный экран WebBrowser необходимо отображать, потому что он дает строку токена. Можно попробовать поэкспериментировать с режимами рекапчи firebase.google.com/docs/auth/web/…
Проблема, с которой я столкнулся сейчас, заключается в том, что на экране веб-браузера иногда не отображается сетка проверочных изображений, а отображается только флажок recaptcha.
хм, никогда не сталкивался с этим. какая модель устройства?
Всплывающее окно не закрывается, оно было на index.html хостинга firebase, я меняю его на captcha.html, и теперь он работает
@Rinat Значит, это работает в iOS, но не в Android. В андроиде поп не закрывается. Удалось заставить его работать в Android?
поддельный firebase.auth.ApplicationVerifier больше не работает. Я вижу это сообщение об ошибке: Ошибка: реализация firebase.auth.ApplicationVerifier.prototype.verify () должна возвращать firebase.Promise, которая разрешается с помощью строки.
@boygiandi, какую версию firebase вы используете? возможно, вам поможет понижение версии (например, до 5.3.1), или вы можете попробовать вернуть собственный объект обещания firebase вместо встроенного обещания (исправив его там: () => Promise.resolve(token))
Это сработало хорошо, когда я загрузил ваш проект и изменил URL-адрес captcha. В моем проекте я связал реагировать и реагировать на тот же проект firebase. Поэтому я разместил одностраничное приложение. Мой вопрос: где я размещаю captcha.html?
Вышеупомянутая проблема была решена. Он хорошо работал на expo client. Он работал хорошо, когда я публиковал на expo. Но когда я создаю файл apk и устанавливаю его на реальном устройстве, приложение внезапно закрывается, показывая ошибку глубокой ссылки без пользовательской схемы. добавлена «схема»: 'myapp' в файле app.json. Теперь приложение открывается, страница captacha загружается после того, как я поставил галочку, она переходит в мое приложение, но загружает поле для ввода номера мобильного телефона с номером мобильного телефона, который я уже добавил. Я получаю otp из firebase, но поле номера телефона загружается вместе с полем ввода otp.
Хорошо @ Ответ Рината был почти идеальным.
Проблема с этой функцией на странице капчи
function sendTokenToApp(token) {
var baseUri = decodeURIComponent(location.search.replace(/^\?appurl\=/, ''));
location.href = baseUri + '/?token=' + encodeURIComponent(token);
}
Работает с iOS (Safary), но оказывается, что 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 приложения expo или браузера, который не открывается из-за проблемы с глубокими ссылками.
Прочитайте это
https://docs.expo.io/versions/latest/workflow/linking#in-a-standalone-app
Вот мое решение, основанное на решении @Rinat.
Основная проблема с предыдущим кодом заключается в том, что firebase.auth().signInWithPhoneNumber никогда не запускается, поскольку он не находится в webView, а firebase> 6.3.3 требует действительного домена для аутентификации.
Я решил использовать React Native Webview, чтобы упростить обмен данными между WebView и Native.
React-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>
Привет, спасибо за решения .. Сработало для меня .. Но для лучшего способа мы можем пропустить капчу, добавив невидимый верификатор капчи.
// Реагировать на нативную сторону
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.info(webViewState.url)
this.onPhoneComplete(webViewState.url)
}
onPhoneComplete = async (url) => {
let token = null
console.info("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.info("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,
},
});
// Сторона капчи. Я использовал хостинг Firebase для размещения этого файла
<!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>
В Android уволить веб-браузер не поддерживает. Итак, я нашел более лучший способ, используя веб-просмотр, он работал у меня на Android и iOS.
Я сделал функциональный эквивалент с хуками состояний на основе версии Damien 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.info(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;
Швейцар обеспечивает поддержку аутентификации телефона Firebase для приложений Expo без отсоединения. Он также поставляется с настраиваемыми компонентами пользовательского интерфейса для Expo / React Native.
Ссылка: https://doorman.cool
Пример кода выглядит примерно так:
export default withPhoneAuth(App, {
doorman: {
publicProjectId: "ID here"
}
})
У тебя есть решение? @ Imdad Hussain