Как написать правило firebase для проверки токена доступа? - PullRequest
0 голосов
/ 19 апреля 2020

У меня настроены следующие правила для моей базы данных 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), у меня есть простой компонент для входа по электронной почте / паролю:

enter image description here

    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.

Спасибо, что много для любой предстоящей помощи.

1 Ответ

1 голос
/ 19 апреля 2020

Мой бэкэнд повторно использует одно и то же приложение для всех запросов, включая запрос на аутентификацию.

Это проблема. Клиентские API, которые вы используете в бэкэнде, просто не предназначены для использования так, как вы используете их сейчас в своем бэкэнде. FirebaseApp, этот пользователь будет оставаться в системе, пока вы не скажете ему выйти. Бэкэнд сохраняет состояние входа в систему и обновляет токен, как в обычном клиентском приложении. Это означает, что маркер авторизации вашего клиента игнорируется после входа в систему, и поэтому у вас фактически нет многопользовательского бэкэнда (все запросы фактически используют того же пользователя, который был ранее вошел в систему) .

Как правило, клиент Firebase выполняет вход самостоятельно и напрямую обращается к данным Firestore с помощью предоставленного SDK, и этот доступ защищен правилами безопасности. Большинство приложений создаются следующим образом.

Если вы не хотите разрешать клиенту прямой доступ к базе данных, вам следует вместо этого:

  • Выполнить вход в клиент напрямую, используя Firebase Auth client SDK
  • Передать маркер идентификатора в каждый запрос к вашему API
  • Бэкэнд должен проверить токен с помощью Firebase Admin SDK
  • Затем бэкэнд может делать запросы для Firestore, используя серверный SDK, , но без ограничения правил безопасности

Если вы go этот маршрут, правила безопасности больше не могут использоваться, так как бэкенд SDK Firestore инициализирован с сервисной учетной записью всегда обходят правила. Бэкэнд должен знать, используя свой собственный лог c, может ли пользователь получить доступ к данным, запрашиваемым у API.

Шаблон, который вы ищете, показан в документации к Firebase Admin SDK для проверка идентификационных токенов .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...