Firebase Demo:
Secured
with Web Component
INTRO
You should now have a good understanding of the basic mechanics of running Firebase through a Web component, and of the need for using Firebase Security on your data and file storage. Here we will put these two together, again just using the Web component, and provided authenticated access.
Firebase offers some 12 different authentication solutions, I will be using the simplest and most straight forward "email/password" option, easy to use, easy to configure, easy to understand. You can see the various options on the Sign in method page under Authentication, and you will need to Enable a method in order to use it. Each user is provided with a "User UID" or "localId" a string of letters and numbers that can be used to identify the user within Firebase data. When a user logs in (signs in), Firebase returns a token that can be used to authenticate their presence in the Firebase project, and what they can and cannot do is dictated by the rules that are set.
As a developer, you are going to need the API key, the Firebase URL, and the Storage Application ID. These can all be found in the project settings for the database and storage. You should also set up a tag/ProjectBucket for the database and a folder for Storage. In both instances I will use FBDEMO for this. For the purposes of the demo I will refer throughout as follows:
API Key === AIzaShC3m_gRuFjgH61xBV2S4ZKJL0m1pTpWRM2
FirebaseURL === https://myProject.firebasio.com/
Storage Application ID === myProject.appspot.com
Project Bucket / Folder === FBDEMO
If I show any user tokens, they will probably be as they are, or shortened for viewing purposes, they will have expired by time of going to print!
For the realtime database, I will use the rules that only allow an authenticated user to write to and read data in their own area, and set just for FBDEMO:
{
"rules": {
"FBDEMO": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
For the firebase storage, I will use rules that allow authenticated users to read and write everything, within the FBDEMO folder:
rules_version = '2';
service firebase.storage {
match /b/myProject.appspot.com/o {
match /FBDEMO/{allPaths=**} {
allow read: if request.auth != null
allow write: if request.auth != null;
}
}
}
Firebase Realtime Database with Secure Rules
SIGN UP a New User
If you have watched some of Taozilla's videos (See resources on the start page) and reviewed the documentation, you will have seen that there is a wide range of "things" you can do with a new user, verifying their email, setting their profile details. For our purposes here, we will just sign up a user.
Url:
https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=<APIKey>
PostText:
email=<emailAddress>&password=<password-Min.6>&returnSecureToken=true
Example:
https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIzaShC3m_gRuFjgH61xBV2S4ZKJL0m1pTpWRM2
email=joe@gmail.com&password=abc123&returnSecureToken=true
Returns: (note "idToken" and "refreshToken" have been shortened)
{
"kind": "identitytoolkit#SignupNewUserResponse",
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImQxMGM4ZjhiMGRjN2Y1...p0olCMoOcyG6Zan2WtSOe8H-2hfw",
"email": "joe@gmail.com",
"refreshToken": "AG8BCnc4UUY_-jgOarPiz8PaxgY_CbG5UK3EEx2X...6C8KMxM33xlZK3YA",
"expiresIn": "3600",
"localId": "uBzLUCxxP9VOuahNseg878JdB6E3"
}
We will need to store the "idToken" and "localId" (uid) to variables for the session.
For auto renew of the idToken we could use the "refreshToken" and the "expiresIn" time in a routine.
The user is now signed up, and also signed in, they could start work. But we also need to see what happens if they sign in...
SIGN IN (LOGIN) a User
Much the same details are required for a sign in, a slightly different url, and different information in the returns:
Url:
https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=APIKey
PostText:
email=<emailAddress>&password=<password-Min.6>&returnSecureToken=true
Example:
https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaShC3m_gRuFjgH61xBV2S4ZKJL0m1pTpWRM2
email=joe@gmail.com&password=abc123&returnSecureToken=true
Returns: (note "idToken" and "refreshToken" have been shortened)
{
"kind": "identitytoolkit#VerifyPasswordResponse",
"localId": "uBzLUCxxP9VOuahNseg878JdB6E3",
"email": "joe@gmail.com",
"displayName": "",
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImQxMGM4Zjhi...IhawGQXLn-gOlVJQ4KxenovzOSUV_wmA",
"registered": true,
"refreshToken": "AG8BCndcseQhtIH2mOXQ-55vqu0wJl6y1...P4D6o515CMNt8pRE",
"expiresIn": "3600"
}
Note the "registered":true option
REFRESH idToken for user
The idToken for a user, once signed in, will last for an hour, then it will need refreshing. You use the refreshToken shown in the signin return above to do this.
Url:
https://securetoken.googleapis.com/v1/token?key=APIKey
PostText:
grant_type=refresh_token&refresh_token=theRefreshToken
Example:
https://securetoken.googleapis.com/v1/token?key=AIzaShC3m_gRuFjgH61xBV2S4ZKJL0m1pTpWRM2
grant_type=refresh_token&refresh_token=AG8BCndcseQhtIH2mOXQ-55vqu0wJl6y1...P4D6o515CMNt8pRE
Returns: (Note "access_token","id_token" and "refresh_token" have been shortened. Note also that the access_token == the id_token)
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc...tRDUzId0_Wgjm3MW6-AkZ1j38a1zXg",
"expires_in": "3600",
"token_type": "Bearer",
"refresh_token": "AMf-vBwPNHd1ZK324sqh7zO59hJlK...GiYBVUYi0TPqgjzT4LAb74QnvYTgYO7NsmR44",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc...tRDUzId0_Wgjm3MW6-AkZ1j38a1zXg",
"user_id": "2HQIFCAifCVDAy5Ogu8VeNhwUPk1",
"project_id": "679881131398"
}
Use the new idToken for access to the firebase data
PUT
Ok, let us get Joe to PUT some data, for example, his age...
Url:
https://<firebaseUrl>/<projectBucket>/<uid>.json?auth=<idToken>
PutText:
{"tag":data}
Example: (idToken shortened)
https://myProject.firebaseio.com/FBDEMO/Dl7oRoAJwwSyfaXXgSnfII0qZ3y2.json?auth=eyJhbGciOiJSUzI1NiIsImtpZCI6ImQxMGM4ZjhiMGRjN2Y1NWUyYjM1
{"age":46}
Returns: {"age":46}
PATCH
This allows the user to "add" items to their firebase list under the uid without removing the existing data!
OK, let us get Joe to PATCH some data, for example, his height in inches...
Url:
https://<firebaseUrl>/<projectBucket>/<uid>.json?auth=<idToken>
PutText:
{"tag":data}
Example: (idToken shortened)
https://myProject.firebaseio.com/FBDEMO/RxmYV2p...raeHTNwH2.json?auth=eyJhbGciOiJSUzI1NiIsImtpZCI6ImFl...ZAcLIt7qcGqtNhA
{"height":67}
Returns: {"height":67}
POST
Now Joe will POST some data, for example, a message...
Url:
https://<firebaseUrl>/<projectBucket>/<uid>.json?auth=<idToken>
PostText:
data
Example: (idToken shortened)
https://myProject.firebaseio.com/FBDEMO/Dl7oRoAJwwSyfaXXgSnfII0qZ3y2.json?auth=eyJhbGciOiJSUzI1NiIsImtpZCI6ImQxMGM4ZjhiMGRjN2Y1NWUyYjM1
Hello, how are you?
Returns: {"name":"-MKfgL5wu3BzefpnA8CX"}
GET
Now Joe will GET some data...
Url:
https://<firebaseUrl>/<projectBucket>/<uid>/<tag>.json?auth=<idToken>
Get:
data
Example: (idToken shortened)
https://myProject.firebaseio.com/FBDEMO/Dl7oRoAJwwSyfaXXgSnfII0qZ3y2/age.json?auth=eyJhbGciOiJSUzI1NiIsImtpZCI6ImQxMGM4ZjhiMGRjN2Y1NWUyYjM1
Returns: 46
DELETE
Now Joe will DELETE a tag and its data
Url:
https://<firebaseUrl>/<projectBucket>/<uid>/<tag>.json?auth=<idToken>
Delete:
Example: (idToken shortened)
https://myProject.firebaseio.com/FBDEMO/Dl7oRoAJwwSyfaXXgSnfII0qZ3y2/age.json?auth=eyJhbGciOiJSUzI1NiIsImtpZCI6ImQxMGM4ZjhiMGRjN2Y1NWUyYjM1
Returns: null
GET (TAGS)
You can GET TAGS using the "?shallow=true" parameter - just like in the no security example
Url:
https://<firebaseUrl>/<projectBucket>/<uid>/<tag>.json?shallow=true&auth=<idToken>
Get:
data
Example: (idToken shortened)
https://myProject.firebaseio.com/FBDEMO/Dl7oRoAJwwSyfaXXgSnfII0qZ3y2.json?shallow=true&auth=eyJhbGciOiJSUzI1NiIsImtpZCI6ImQxMGM4ZjhiMGRjN2Y1NWUyYjM1
Returns: {"-MKflx2zqr6UKd_WIroh":true,"-MKflxY6PSR1rvfDMfub":true,"-MKflxpUp0sVf71CQLeF":true}
Firebase Storage with Secure Rules
UPLOAD A FILE
In order for this to work, you need to have captured the secureToken (idToken) when an authenticated user signed in (logged in). This token is then used in the web headers as an Authorization / Bearer [token]. If the secureToken is not valid, then the file upload will fail. Also ensure that the mimetype is set correctly.
Header:
content-type : <mimetype>
Authorization : Bearer <idToken>
Url:
https://firebasestorage.googleapis.com/v0/b/<applicationID>/o/<folder>%2F<filename>?alt=media
PostFile:
<full/path/to/filename>
Example: (idToken is shortened)
[
["content-type", "image/png"],
["Authorization", "Bearer eyJhbGciOiJSUzI1...YacwA"]
]
https://firebasestorage.googleapis.com/v0/b/myProject.appspot.com/o/FBDEMO%2Fmyimage1.png?alt=media
/storage/emulated/0/Android/data/edu.mit.appinventor.aicompanion3/files/myimage1.png
Note: (something changed ?) the above blocks are showing an absolute path to the file for PostFile. It may be that you need a full path, e.g to include "file://" before the rest of the path shown if you get an error 1104 showing up.
Returns:
{
"name": "FBDEMO/myimage1.png",
"bucket": "myProject.appspot.com",
"generation": "1603884334214831",
"metageneration": "1",
"contentType": "image/png",
"timeCreated": "2020-10-28T11:25:34.214Z",
"updated": "2020-10-28T11:25:34.214Z",
"storageClass": "STANDARD",
"size": "38106",
"md5Hash": "ZGOY4M+44ZmWCTAHLvtCtQ==",
"contentEncoding": "identity",
"contentDisposition": "inline; filename*=utf-8''myimage1.png",
"crc32c": "AlvaFg==",
"etag": "CK/dgeCW1+wCEAE=",
"downloadTokens": "05e71cdd-5b56-442a-ab79-9e07e64ecdc6"
}
The developer needs to capture and store the "name" and the "downloadTokens" for accessing the file
DOWNLOAD A FILE
(get/create the download url to access the file)
There are two options here, and this depends on how you want to treat the uploaded file. If you want to keep the file inside the secure "bubble", and therefore only accessible to authenticated users, then the user will need to employ their secureToken for Authorization again, and build a url WITHOUT the "downloadTokens". In AppInventor, this may mean the only way to access the file is to actually download it to your device using the Web component. Alternatively, we can build a url that includes the "downloadTokens", which makes the file world readable, although of course you have to have the url and the "downloadTokens" to be able to access the file. In App Inventor this relaxes the need to download the file, for an image, for example, you can set the image.Picture to the url.
In either case, the url required is very similar to the one used to upload....
Header:
content-type : <mimetype>
Authorization : Bearer <idToken>
Secure Url:
https://firebasestorage.googleapis.com/v0/b/<applicationID>/o/<folder>%2F<filename>?alt=media
Example:
https://firebasestorage.googleapis.com/v0/b/myProject.appspot.com/o/FBDEMO%2Fmyimage1.png?alt=media
---------------------------------------------------------------------------------------------
World Readable Url:
https://firebasestorage.googleapis.com/v0/b/<applicationID>/o/<folder>%2F<filename>?alt=media&token=<downloadTokens>
Example:
https://firebasestorage.googleapis.com/v0/b/myProject.appspot.com/o/FBDEMO%2Fmyimage1.png?alt=media&token=05e71cdd-5b56-442a-ab79-9e07e64ecdc6
World readable - will display image in image component
Secure Download - will download file to device
DELETE A FILE
Very similar to the Secure Download, just replace Web1.Get with Web1.Delete, and remove the File Save setting blocks
Url:
https://firebasestorage.googleapis.com/v0/b/<applicationID>/o/<folder>%2F<filename>?alt=media
Delete:
Example:
https://firebasestorage.googleapis.com/v0/b/myProject.appspot.com/o/FBDEMO%2Fmyimage1.png?alt=media
Returns: nothing is returned on delete
In Summary:
I made a video of the process for the no rules version on the previous page, if you need to see the process in action (the secure rules method looks the same on screen)
I guess it is worth a warning that Google like to change things without notice or advice, therefore parts or all of this may stop working, or it all gets completely replaced with something else (hopefully a bit easier to handle). I have seen that "identityToolkit" is on its way out, this will probably be replaced with something "firebasey"....
If you are following the methods you have seen in this guide, then take your time, be precise, test and test again to ensure you have everything right, and you will have yourself an App Inventor app that can access Firebase Realtime database and Firebase Storage, with secure rules, without having to use the built-in experimental firebase component, or to need to use any of the extensions provided to "make this easier"......
Thanks for watching :)