前提・実現したいこと
Cloud Storageのセキュリティルールの仕様(?)がいま一つ分かりません。
実現したいこととしては、Firebaseの認証機能を通ったユーザだけがCloud Storage内の画像等にアクセスできるようにするというものです。
今、下記リンク先の記事のようにしてCloud Storage内の画像を表示しました。
Firebase Cloud Storageに格納した画像を一覧表示する - Qiita
つまり、Firestoreに格納された下記値を取得して、img
タグに突っ込むということをしています。
javascript
1downloadUrl: `https://firebasestorage.googleapis.com/v0/b/${bucketName}/o/${encodeURIComponent(filePath)}?alt=media`
その後、下記のように誰でも読み込み可能なセキュリティルールから、
javascript
1service firebase.storage { 2 match /b/{bucket}/o { 3 match /{allPaths=**} { 4 allow read; 5 allow write: if request.auth != null; 6 } 7 } 8}
Firebaseのドキュメントをもとに、下記のようにユーザ認証をパスしたユーザだけが読み込み可能というセキュリティルールに変更しました。
javascript
1service firebase.storage { 2 match /b/{bucket}/o { 3 match /{allPaths=**} { 4 allow read, write: if request.auth != null; 5 } 6 } 7}
このようなセキュリティルールに書き換えたところ、ユーザ認証をパスしたユーザであっても画像を表示できなくなってしまいました(403エラーが返ってきます)。
ユーザ認証をパスしているはずなのになぜ画像が表示できなくなってしまったのでしょうか?
何かご回答を頂けると助かります<(_ _)>
追記
Reactのコードですが、
ユーザ認証を行う下記コンポーネント内でuid
が取得出来たら配下のコンポーネントを表示し、取得できなければログインページへリダイレクトさせるという処理をし確認しています。
jsx
1export default class Auth extends React.Component { 2 constructor(props) { 3 super(props); 4 this.state = { 5 uid: null, 6 loading: true, 7 }; 8 } 9 10 componentDidMount() { 11 this.removeListener = firebase.auth().onAuthStateChanged((user) => { 12 if (user) { 13 this.setState({ 14 uid: user.uid, 15 loading: false, 16 }); 17 } else { 18 this.setState({ 19 loading: false, 20 }); 21 } 22 }); 23 } 24 25 render() { 26 if (this.state.loading) { 27 return ( 28 <p>Now Loading...</p> 29 ); 30 } 31 32 if (!this.state.uid) { 33 return ( 34 <Redirect to="/" /> 35 ); 36 } 37 38 return ( 39 this.props.children 40 ); 41 } 42}
そして、uid
が取得できた場合に表示するコンポーネントの中で、
javascript
1downloadUrl: `https://firebasestorage.googleapis.com/v0/b/${bucketName}/o/${encodeURIComponent(filePath)}?alt=media`
が格納されたFirestoreのドキュメントを下記のようにまとめて取得し、<img>
に突っ込んでいっています。
javascript
1const db = firebase.firestore(); 2const datas = []; 3 4db.collection('images').get() 5 .then((snapshot) => { 6 snapshot.forEach((doc) => { 7 const data = doc.data(); 8 datas.push(data); 9 }); 10 this.setState({ 11 imageDatas: datas, 12 }); 13 });
追記2
色々と試しているのですが、下記のようにgetDownloadURL()
を使って画像のダウンロードURLを取得し、表示させようとすると、ちゃんとユーザの認証状態によって画像の表示・非表示ができます。
javascript
1const storage = firebase.storage(); 2const fileRef = storage.ref().child('path to file'); 3fileRef.getDownloadURL().then((url) => { 4 this.setState({ 5 downloadUrl: url, 6 }); 7}).catch((err) => { 8 console.log(err); 9});
つまり、getDownloadURL()
で画像を表示せずに、
Firestoreに記述された
javascript
1downloadUrl: `https://firebasestorage.googleapis.com/v0/b/${bucketName}/o/${encodeURIComponent(filePath)}?alt=media`
を利用して画像を表示させようとしていることに問題があるのではないかと思っています。
(末尾のトークン(?)を省いているのが原因なのかどうなのか...)
追記2_1
ダウンロードURLの末尾のトークンを省いていることに問題がありそうだったので、
javascript
1downloadUrl: `https://firebasestorage.googleapis.com/v0/b/${bucketName}/o/${encodeURIComponent(filePath)}?alt=media&token=hogehoge...`
のように(コンソールから確認できるダウンロードURLで)記述したところ、認証状態によって画像の表示・非表示ができました。
(ただこのトークンが何なのかを今一つ理解せずに実験してみての結果なのでトークンによるものなのかは断言できないです...)
しかし、末尾のトークンを正しくつけてやればいいのではないかということは分かったのですが、個人的に考える重大な問題があります。
それは「ダウンロードURLを<img>
に突っ込んでいるわけなので、ブラウザのディベロッパツールなどからダウンロードURLを見れば、簡単に外部からでもアクセスできてしまう」というものです。
ユーザ認証をパスした者が悪意あるユーザにダウンロードURLを教えた場合、そのダウンロードURLにアクセスしまくり、Firebaseの料金に影響を及ぼすことはできるわけですよね?
ちなみにですが、末尾のトークンを含まないダウンロードURLで外部からアクセスした場合は403が返ってき、アクセスできません。
なので理想としては、
「ユーザ認証をパスしたユーザにのみCloud Storage上の画像を表示させる。しかも画像を表示させるためのダウンロードURLは末尾のトークンを抜いた形式で」
という手法です。
この手法を実現する方法はないでしょうか?...
下記リンク先で同じ疑問を持った方がいるようなのですが、回答にあるようなセキュリティルールでは誰でもアクセスできてしまうので...
Access url without token in Firebase Storage - Stack Overflow