У меня настроены следующие правила для моей базы данных firebase:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow write: if request.auth.uid != null;
}
}
}
Я хочу создать правило записи, которое позволяет пользователю писать только при наличии действительного токена доступа. Я не думаю, что вышеприведенное правило является правильным, и я очень озадачен тем, как Firebase отслеживает авторизацию пользователя.
Вот как я настроил свое приложение:
Вкл. Интерфейс (React), у меня есть простой компонент для входа по электронной почте / паролю:
axios.get('/auth', {
headers: {
username: this.usernameRef.current.value,
password: this.passwordRef.current.value
}
}).then(...).catch(...);
Мой бэкэнд (Node.js / express ) получает запрос:
const fb = require('firebase');
const initFirebase = () => {
const config = {
apiKey: "***",
authDomain: "***",
databaseURL: "***",
projectId: "***",
storageBucket: "***",
messagingSenderId: "***",
appId: "***",
measurementId: "***"
};
const app = fb.initializeApp(config);
return app.firestore();
}
router.get('/', (req, res, next) => {
const email = req.headers.username;
const password = req.headers.password;
let fbApp;
if (!fb.apps.length) {
try {
initFirebase();
} catch (err) {
res.status(500).send('Error initializing Firebase.');
return;
}
}
fbApp = fb.apps[0];
fbApp.auth().signInWithEmailAndPassword(email, password).then(user => {
res.status(200).send(JSON.stringify(user));
}).catch(err => {
res.status(401).send();
});
});
Если запрос, отправленный в Firebase для аутентификации пользователя, выполнен успешно, он возвращает объект пользователя с маркером доступа внутри него. Я возвращаю пользовательский объект в интерфейс. Интерфейс хранит токен доступа в localStorage и в State. Затем для любых последующих запросов к firebase он вставляет токен в заголовки запроса.
Например, если я хочу добавить новый пост в свой блог, я делаю это так:
внешний интерфейс:
axios.post('/blogs', {
title: this.state.newPost.title,
body: this.state.newPost.body
}, {
headers: {Authorization: this.state.accessToken}
}).then(response => {...}, err => {...});
Бэкэнд получает его и делает следующее:
const fb = require('firebase');
const initFirebase = () => {
const config = {
apiKey: "***",
authDomain: "***",
databaseURL: "***",
projectId: "***",
storageBucket: "***",
messagingSenderId: "***",
appId: "***",
measurementId: "***"
};
const app = fb.initializeApp(config);
return app.firestore();
}
router.post('/', (req, res, next) => {
if (!req.headers.authorization) {
res.status(401).status('Unauthorized');
return;
}
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', () => {
const data = JSON.parse(chunks);
const post = {};
post.title = data.title;
post.body = data.body;
post.createdAt = Date.now();
post.updatedAt = post.createdAt;
let firestore;
if (fb.apps.length) firestore = fb.apps[0].firestore();
else {
try {
firestore = initFirebase();
} catch (err) {
res.status(500).send('Error initializing Firebase.');
return;
}
}
firestore.collection('blogposts').add(post).then(docRef => {
res.status(200).send(JSON.stringify({id: docRef.id, createdAt: post.createdAt}));
}).catch (err => {
res.status(500).send('Error posting blog post.');
});
});
});
В простом сценарии использования ios, это работает.
Что я Я не понимаю, как работают правила безопасности для каждого пользователя. Похоже, что request.auth.uid (в приведенном выше правиле записи) задан, пока пользователь (любой пользователь) проходит проверку подлинности.
Я попытался удалить токен доступа на внешнем интерфейсе, удалив его из localStorage и государство, тем самым имитируя пользователя, не входящего в систему. Затем я попытался создать новый пост. Это сработало. Поэтому Firebase, очевидно, не нужно, чтобы я отправлял действительный токен доступа для прохождения указанного выше правила записи.
Я пытался создать запрос на firebase напрямую из совершенно другого приложения (написано в Node.js):
const fb = require('firebase');
const config = {
apiKey: "***",
authDomain: "***",
databaseURL: "***",
projectId: "***",
storageBucket: "***",
messagingSenderId: "***",
appId: "***",
measurementId: "***"
};
const store = fb.initializeApp(config).firestore();
const post = {
title: 'blog #14',
body: 'This is blog #14.',
createdAt: Date.now(),
updatedAt: Date.now()
}
store.collection('blogposts').add(post).then(docRef => {
console.log('docRef.id = ', docRef.id);
}).catch (err => {
console.log('err=', err);
});
Ошибка: ошибка PERMISSION_DENIED из Firebase. Это говорит о том, что приложение Firebase, созданное в initializeApp (...), должно как-то с этим связано. Мой бэкэнд повторно использует одно и то же приложение для всех запросов, включая запрос на аутентификацию. Это боковое приложение создает совершенно новое приложение Firebase. Это приложение, которое отслеживает, аутентифицирован ли пользователь или нет? Сохраняет ли он копию токена доступа и неявно отправляет ее со всеми запросами в Firebase?
Как ни странно, этот тест, казалось, де-авторизовал моего основного пользователя. Вернувшись в исходное приложение, я начал получать ту же ошибку PERMISSION_DENIED. Мне пришлось удалить токен доступа из localStorage, обновить sh страницу, снова войти в систему, и это, казалось, сбросило его.
Как написать правило Firebase, которое проверяет токен доступа? И как мне отправить токен доступа с запросами в Firebase? Если это все в приложении Firebase, используемом для отправки запросов, как я могу убедиться, что для каждого человека используется другое приложение?
Что действительно поможет, так это описание того, что веб-интерфейс должен делать для запуска процесса (где маркер доступа отправляется, если необходимо), что бэкэнд должен делать с этим запросом, как бэкэнд должен вызывать Firebase, и как должно выглядеть правило Firebase.
Спасибо, что много для любой предстоящей помощи.