I am trying to build a chat application using firebase.
The structure for message table :
message -
$message_id
- $message_push_id
- message {
sender : 3,
receiver : 58,
token : token_of_sender,
message : hi
....}
message_id here is generated using the sender and receiver ids "3_58"
I am using push to save messages into firebase.
{
"rules": {
".read": true,
"message":
{
"$messageid": {
"$messagepushid":
{
".read": true,
".write": "auth != null && !data.exists()",
".indexOn": ["token", "userid", "receiverid", "sent_time"],
".validate": "auth.token == newData.child('token').val() && newData.hasChildren(['token', 'userid', 'receiverid', 'text'])"
}
}
}
}
}
I have already generated token using custom token generator :
Firebase firebase = getFirebase();
Map<String, Object> authPayload = new HashMap<String, Object>();
authPayload.put("uid", user.getUserid());
authPayload.put("token", user.getToken());
TokenGenerator tokenGenerator = new TokenGenerator(Constants.FIREBASE_KEY);
TokenOptions tokenOptions = new TokenOptions();
tokenOptions.setAdmin(false);
final String firebaseToken = tokenGenerator.createToken(authPayload, tokenOptions);
firebase.authWithCustomToken(firebaseToken, new Firebase.AuthResultHandler() {
#Override
public void onAuthenticated(AuthData authData) {
Log.d("Auth", "Success : " + authData.toString());
Log.d("Auth", "Token : " + firebaseToken);
SharedPrefs.setFirebaseUserToken(getActivity(), firebaseToken);
}
#Override
public void onAuthenticationError(FirebaseError
firebaseError) {
firebaseError.toException().printStackTrace();
}
});
I am trying to push a new message but I am getting error :
RepoOperation﹕ setValue at /message/3_58/-Jy2We4cqLjuQNF6Oyhs failed: FirebaseError: Permission denied
I am unable to figure out where I am going wrong.
This is the code to send chat :
mConversationReferenceFireBase = mFireBase.child("message").child(mConversationId);
Chat conversation = new Chat( mToken, mUserId, mReceiverId, message );
mConversationReferenceFireBase.push().setValue(conversation, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
if (firebaseError != null) {
Log.e("Conversation", firebaseError.toString());
}
}
});
mConversationId = 3_58
The token here is generated for a user. We have a separate server to maintain the user accounts. The token is being used to upload/ download any files, the firebase is used as Chat Server.
With the rules set to .read = true and .write = true; everything works, however when I am attempting to have an authentication performed, it results in the error mentioned above. I've tried using the token from token generator, to check if I may possibly be using the wrong token.
I am following this example to generate token for firebase auth :
https://www.firebase.com/docs/web/guide/login/custom.html
Since storing a firebase secret key is bad in terms of security, what other alternative can be followed to generate a token for authentication?
I was too stuck on this point and here's what helped me.
First things first, there are two types of users who can access database from firebase
Authorized
Non-authorized
By default it is set to non-authorized but then they do not have any permissions neither read nor write, so initially if you try to perform any operation you get the permission denied error.
So basically one has to change the required permissions on the firebase console in-order to access the database.
Complete answer here
Check the rule defined in your firebase account and also the simulator options. Description is given below in a image.
Related
I am currently in the process of getting my project to run with Firebase. I've completed authentication through this script here.
using System.Collections.Generic;
using UnityEngine;
using GooglePlayGames;
using GooglePlayGames.BasicApi;
using UnityEngine.SocialPlatforms;
using System.Threading.Tasks;
using Firebase;
using Firebase.Auth;
public class FirebaseManager : MonoBehaviour
{
private string AuthCode;
private void Start()
{
// Initialize Play Games Configuration and Activate it.
PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder()
.RequestServerAuthCode(false)
.Build();
Debug.LogWarning("Config Built");
PlayGamesPlatform.InitializeInstance(config); Debug.LogWarning("Instance");
PlayGamesPlatform.Activate(); Debug.LogWarning("Activate");
Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance; // Sign In and Get a server auth code.
UnityEngine.Social.localUser.Authenticate((bool success) =>
{
if (!success)
{
Debug.LogError("SignInOnClick: Failed to Sign into Play Games Services.");
return;
}
string authCode = PlayGamesPlatform.Instance.GetServerAuthCode();
if (string.IsNullOrEmpty(authCode))
{
Debug.LogError("SignInOnClick: Signed into Play Games Services but failed to get the server auth code.");
return;
}
Debug.LogFormat("SignInOnClick: Auth code is: {0}", authCode); // Use Server Auth Code to make a credential
Firebase.Auth.Credential credential = Firebase.Auth.PlayGamesAuthProvider.GetCredential(authCode); // Sign In to Firebase with the credential
auth.SignInWithCredentialAsync(credential).ContinueWith(task => {
if (task.IsCanceled)
{
Debug.LogError("SignInOnClick was canceled.");
return;
}
if (task.IsFaulted)
{
Debug.LogError("SignInOnClick encountered an error: " + task.Exception);
return;
}
Firebase.Auth.FirebaseUser newUser = task.Result;
Debug.LogFormat("SignInOnClick: User signed in successfully: {0} ({1})", newUser.DisplayName, newUser.UserId);
});
});
}
}
Once i build this project onto Google Play and test it, it has the google UI pop up that shows my Google Play account name. Checking firebase authentication panel i can see my google play account populate the field with a UID.
I send some data to my database via REST API
using System.Collections.Generic;
using UnityEngine;
using Firebase.Database;
using Proyecto26;
using Firebase;
using Firebase.Auth;
using UnityEngine.UI;
using TMPro;
public class Database : MonoBehaviour
{
#region Instance
public static Database I;
public void GetInstance()
{
if(I == null)
{
I = this;
}
else
{
Destroy(I);
I = this;
}
}
private void Awake()
{
GetInstance();
}
#endregion
private string UID;
PlayerSaveData playerSaveData = new PlayerSaveData();
private void Start()
{
Debug.Log("Sending data");
LoadedData();
}
public void SavePlayerData()
{
UserID();
Debug.Log(UID);
RestClient.Put("https://-censored project linkrtdb.firebaseio.com/users" + UID + ".json", playerSaveData);
Debug.Log("Sent Data function complete");
}
public void LoadedData()
{
RestClient.Get<PlayerSaveData>("https://-censored project link-rtdb.firebaseio.com/users" + UID + ".json").Then(response =>
{
playerSaveData = response;
});
}
// UID //
public void UserID()
{
Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
Firebase.Auth.FirebaseUser user = auth.CurrentUser;
if (user != null)
{
string playerName = user.DisplayName;
// The user's Id, unique to the Firebase project.
// Do NOT use this value to authenticate with your backend server, if you
// have one; use User.TokenAsync() instead.
string uid = user.UserId;
Debug.LogWarning("Player UID: " + uid);
UID = uid;
}
else
{
UID = "No UID";
}
}
}
Once i check my data on the Database page on the Firebase Console on a live build via Google Play Market, the data only populates when i set the rules to test "Write = true and read = true for everyone"
However, once i add the rules from the documents that fit my situation
{
"rules": {
"users": {
"$uid": {
// Allow only authenticated content owners access to their data
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
}
The live application does not continue to write to the database.
My debug log on Logcat shoes the UID is spitting out the same exact UID that is authenticated on firebase.
I hate to be the guy that floods a forum with a question that i am sure has been asked a 100 times. I have been at this firebase thing for about 2 days, but i am throwing in the towel on this one. I can't find anything unique to my situation. With the little knowledge i have, i do feel that somehow the authentication is not sending with the way i structured my database.cs with REST. But i was assured this is the way i should go.
Any help would be greatly appreciated, much thanks to those that take the time! :D
It seems like your REST client is not passing the user credentials along with the request. The Firebase SDK passes this information with each connection/request, and you'll have to do the same here.
Have a look at the Firebase documentation on authorizing REST requests, specifically the section on authenticating with an ID token, which is probably easiest for you.
On the other hand, I'd recommend having a look using using the Firebase Realtime Database SDK instead of calling the REST API, as this will pass the required information automatically.
I cannot get my Android device to receive a test variant during the AB experiment.
What i did
In Firebase admin panel, I created a variable with this value:
And created an AB test with three variants:
Then I got an instance token as per docs:
FirebaseInstallations.getInstance().getToken(/* forceRefresh */true)
.addOnCompleteListener(new OnCompleteListener<InstallationTokenResult>() {
#Override
public void onComplete(#NonNull Task<InstallationTokenResult> task) {
if (task.isSuccessful() && task.getResult() != null) {
Log.d("Installations", "Installation auth token: " + task.getResult().getToken());
promise.resolve(task.getResult().getToken());
} else {
Log.e("Installations", "Unable to get Installation auth token");
}
}
});
and added it to my test devices list:
What happened
However, after rebooting the app (I also cleaned the cache and force-stopped it just in case it cached something) it still receives the original default value:
...
Map<String, Object> responseMap = new HashMap<>(2);
responseMap.put("result", result);
responseMap.put("constants", module.getConstantsForApp(FirebaseApp.DEFAULT_APP_NAME));
Log.d("REMOTECONFIG", responseMap.toString());
...
in the logs:
10-04 10:25:12.886 16964 16964 D REMOTECONFIG: {result=true, constants={minimumFetchInterval=43200, fetchTimeout=60, values={testValue=Bundle[{source=remote, value=somedefault}]}, lastFetchTime=1601796312020, lastFetchStatus=success}}
Why is that? what am I missing? I doublechecked my token, verified it with jwt.io, it is valid and not expired.
I am a Unity programmer and I am using Firebase to manage user accounts. I tried to set up Facebook Login. No problems with the Facebook sdk and I can log in successfully. However, when the credential returned by Facebook sdk is used as a parameter of FirebaseAuth.DefaultInstance.SignInWithCredentialAsync, it returns internal error.
And here is my code:
void authCallBack(IResult result) {
if (result.Error != null) {
Debug.Log(result.Error);
}
else {
if (FB.IsLoggedIn) {
Debug.Log("Log in successfully.");
AccessToken token = AccessToken.CurrentAccessToken;
Credential credential = FacebookAuthProvider.GetCredential(token.TokenString);
accessToken(credential);
}
else
Debug.Log("not logged in");
}
}
public void accessToken(Credential firebaseResult) {
FirebaseAuth auth = FirebaseAuth.DefaultInstance;
Debug.Log("Auth CurrentUser: " + FirebaseAuth.DefaultInstance.CurrentUser);
if (!FB.IsLoggedIn){
return;
}
if (auth.CurrentUser != null && !string.IsNullOrEmpty(auth.CurrentUser.UserId)){
Debug.Log("CurrentUser ID: " + auth.CurrentUser.UserId);
auth.CurrentUser.LinkAndRetrieveDataWithCredentialAsync(firebaseResult).ContinueWith(task =>
{
if (task.IsCanceled || task.IsFaulted)
{
Debug.LogError("LinkWithCredentialAsync encountered an error: " + task.Exception);
// TODO: Show error message to player
return;
}
FirebaseUser newUser = task.Result.User;
Debug.LogFormat("Credentials successfully linked to Firebase user: {0} ({1})",
newUser.DisplayName, newUser.UserId);
});
} else {
auth.SignInWithCredentialAsync(firebaseResult).ContinueWith(task =>
{
if (task.IsCanceled || task.IsFaulted) {
Debug.LogError("SignInWithCredentialAsync encountered an error: " + task.Exception.InnerExceptions[0].Message);
// TODO: Show error message to player
return;
}
FirebaseUser newUser = task.Result;
Debug.LogFormat("Credentials successfully created Firebase user: {0} ({1})",
newUser.DisplayName, newUser.UserId);
});
}
}
More details in VS Debugging:
When I test it on my Android device, it comes out an error message g_methods_cached only.
Can anyone help?
P.S. Here is another question asked yesterday and I don't know if it is relevant.
FirebaseAuthWebException not found. Please verify the AAR
Oh, I have made a silly mistake!
In the Facebook Developer page, there is the App Secret in Setting > Basic. And it has to be added into Firebase Console with the App ID. No problem right now. And then......
I just copied the App Secret without showing and pasted into Firebase Console.
Which means I have set 8 black dots (●●●●●●●●) as my App Secret in my Firebase Console. I know it is too silly. But just in case there is someone careless like me.
I had a working app with Facebook & Email Login feature, since I upgrade the Firebase console (only, the sdk has not been update).
The app release before the Firebase 3.0 was working before, but it is not anymore able to sign/log with Facebook after the console has been upgraded.
What I have done:
1 - Upgraded the Firebase console
Because of Firebase & Facebook console update, I also had to put the Oauth Callback to the Facebook App
2 - Pasted the Firebase Facebook OAuth Callback to the Facebook console (before it was void) `https://xxx.firebaseapp.com/__/auth/handler``
The Exception:
The firebase Auth listener trigger a Firebase Error :
Invalid authentication credentials provided. and Facebook :
{"providerErrorInfo":{"code":400,"message":"Unsuccessful debug_token
response from Facebook: {\"error\":{\"message\":\"(#100) You must
provide an app access token or a user access token that is an owner or
developer of the
app\",\"type\":\"OAuthException\",\"code\":100,\"fbtrace_id\":\"DG4lLRJHFBS\"}}"}}
The FirebaseError Code:
In the decompiled code of the FirebaseAndroidSdk, the error object is:
0 = {java.util.LinkedHashMap$LinkedEntry#22680} "code" ->
"INVALID_CREDENTIALS"
1 = {java.util.LinkedHashMap$LinkedEntry#22681}
"message" -> "Invalid authentication credentials provided."
2 = {java.util.LinkedHashMap$LinkedEntry#22682} "details" ->
"{"providerErrorInfo":{"code":400,"message":"Unsuccessful debug_token
response from Facebook: {\"error\":{\"message\":\"(#100) You must
provide an app access token or a user access token that is an owner or
developer of the app\",\"type\":\"OAuthException\",\"code\":100,\"fbtrace_id\":\"BtB3JF2qmku\"}}"}}"
with the decompiled code:
private void makeAuthenticationRequest(String urlPath, Map<String, String> params, AuthResultHandler handler) {
final AuthenticationManager.AuthAttempt attempt = this.newAuthAttempt(handler);
this.makeRequest(urlPath, HttpRequestType.GET, params, Collections.emptyMap(), new RequestHandler() {
public void onResult(Map<String, Object> result) {
Object errorResponse = result.get("error");
String token = (String)Utilities.getOrNull(result, "token", String.class);
if(errorResponse == null && token != null) {
if(!AuthenticationManager.this.attemptHasBeenPreempted(attempt)) {
AuthenticationManager.this.authWithCredential(token, result, attempt);
}
} else {
FirebaseError error = AuthenticationManager.this.decodeErrorResponse(errorResponse);
AuthenticationManager.this.fireAuthErrorIfNotPreempted(error, attempt);
}
}
public void onError(IOException e) {
FirebaseError error = new FirebaseError(-24, "There was an exception while connecting to the authentication server: " + e.getLocalizedMessage());
AuthenticationManager.this.fireAuthErrorIfNotPreempted(error, attempt);
}
});
}
At AuthListener level, the firebaseError code : -20
https://www.firebase.com/docs/java-api/javadoc/com/firebase/client/FirebaseError.html
The specified authentication credentials are invalid.
The Facebook Error Code:
code 400
Nothing relevant found here : https://developers.facebook.com/docs/graph-api/using-graph-api/#errors
The code for Authing:
public void authWithFirebase(final String provider, Map<String, String> options) {
if (options.containsKey(AUTH_OPTIONS_ERROR)) {
EventBus.getDefault().post(new MessageToDisplayEvent(options.get(AUTH_OPTIONS_ERROR), true));
} else {
if (provider.equalsIgnoreCase(AUTH_PROVIDER_TWITTER)) {
// if the provider is twitter, we must pass in additional options, so use the options endpoint
ref.authWithOAuthToken(provider, options, new AuthResultHandler(provider));
} else {
// if the provider is not twitter, we just need to pass in the oauth_token
ref.authWithOAuthToken(provider, options.get(AUTH_OPTIONS_TOKEN), new AuthResultHandler(provider));
}
}
}
TOKEN Validity:
From the code above, the Token is confirmed valid since :
https://graph.facebook.com/app?access_token=%7Byour_access_token%7D return a valid JSON
And the Facebook Tool AccessToken https://developers.facebook.com/tools/debug/accesstoken return a still valid TOKEN
What changed from user point of view:
Now, When I click on the FacebookLoginButton, I have a new dialog that ask "connection as %FacebookUserName", with 2 buttons ("Unconnect" & "Cancel")
I posted a bug report at Firebase, but I even do not know if this is Facebook or Firebase, any help, advise for exploring new issue surface or solution is welcome.
In Facebook Developper Console, switch-off the option about the "app key that is integrated in the client".
For me this changed the behavior. I will give more information as far I get from Firebase/Facebook
Here is a French Screenshot to help you setting up Facebook:
I am using couchbase mobile for an application and I want to use facebook for authentication. As per documentation, couchbase offers it's own implementation for authentication, the only required thing would be the token which I retrieve from the android facebook login flow.
The code for Synchronize class looks something like this:
public class Synchronize {
public Replication pullReplication;
public Replication pushReplication;
public static class Builder {
public Replication pullReplication;
public Replication pushReplication;
public Builder(Database database, String url, Boolean continuousPull) {
if (pullReplication == null && pushReplication == null) {
URL syncUrl;
try {
syncUrl = new URL(url);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
pullReplication = database.createPullReplication(syncUrl);
pullReplication.setContinuous(true);
pushReplication = database.createPushReplication(syncUrl);
pushReplication.setContinuous(true);
}
}
public Builder facebookAuth(String token) {
if (!TextUtils.isEmpty(token)) {
Authenticator facebookAuthenticator = AuthenticatorFactory.createFacebookAuthenticator(token);
pullReplication.setAuthenticator(facebookAuthenticator);
pushReplication.setAuthenticator(facebookAuthenticator);
}
return this;
}
public Builder basicAuth(String username, String password) {
Authenticator basicAuthenticator = AuthenticatorFactory.createBasicAuthenticator(username, password);
pullReplication.setAuthenticator(basicAuthenticator);
pushReplication.setAuthenticator(basicAuthenticator);
return this;
}
public Builder addChangeListener(Replication.ChangeListener changeListener) {
pullReplication.addChangeListener(changeListener);
pushReplication.addChangeListener(changeListener);
return this;
}
public Synchronize build() {
return new Synchronize(this);
}
}
private Synchronize(Builder builder) {
pullReplication = builder.pullReplication;
pushReplication = builder.pushReplication;
}
public void start() {
pullReplication.start();
pushReplication.start();
}
public void destroyReplications() {
if (pullReplication != null && pushReplication != null) {
pullReplication.stop();
pushReplication.stop();
pullReplication.deleteCookie("SyncGatewaySession");
pushReplication.deleteCookie("SyncGatewaySession");
pullReplication = null;
pushReplication = null;
}
}
}
And I use it like this:
...
public void startReplicationSync(String facebookAccessToken) {
if (sync != null) {
sync.destroyReplications();
}
final String url = BuildConfig.URL_HOST + ":" + BuildConfig.URL_PORT + "/" + DATABASE_NAME;
sync = new Synchronize.Builder(databaseManager.getDatabase(), url, true)
.facebookAuth(facebookAccessToken)
.addChangeListener(getReplicationChangeListener())
.build();
sync.start();
}
...
My sync gateway json config file:
{
"interface":":4984",
"adminInterface":":4985",
"log":["REST"],
"facebook":{
"register" : true
},
"databases":{
"sync_gateway":{
"server":"http://localhost:8091",
"bucket":"sync_gateway",
"users": {
"GUEST": {"disabled": false}
},
"sync":`function(doc) {channel(doc.channels);}`
}
}
}
I also tried with "GUEST": {"disabled": true}, no luck
My problem is that if I do this
pullReplication.setAuthenticator(facebookAuthenticator);
pushReplication.setAuthenticator(facebookAuthenticator);
Nothing will ever get replicated/pulled from the server. However if I don't set an authenticator, everything is pulled. Is it something I am doing wrong? I really need to use the authenticator in order to prevent some documents to not being replicated for non-authenticated users.
Note! The token is good, as if I am looking in the users section of sync gateway admin, I can see the right profile id of the logged in user token I passed to the couchbase facebook authenticator.
In the Sync Gateway config you provided, the Sync Function is function(doc, oldDoc) {channel(doc.channels);} which means that if the document processed by Sync Gateway contains a string(s) under the channels field, the document will be mapped to this/these channel(s). Let's assume the following config file:
{
"log": ["CRUD"],
"databases": {
"db": {
"server": "walrus:",
"users": {
"GUEST": {"disabled": false, "admin_channels": ["*"]}
},
"sync": `
function sync(doc, oldDoc) {
channel(doc.channels);
}
`
}
}
}
If the channels field doesn't exist then the document will be mapped to a channel called undefined. But the GUEST account has access to the * channel (a placeholder to represent all channels). So, all unauthenticated replications will pull all documents. Let's now introduce the facebook login field in the config file. This time, replications authenticated with a facebook token represent a new user which has only access to the ! channel by default (watch this screencast to understand the ! channel, a.k.a the public channel https://www.youtube.com/watch?v=DKmb5mj9pMI). To give a user access to other channels, you must use the access API call in the Sync Function (read more about all Sync Function API calls here).
In the case of facebook authentication, the user's facebook ID is used to represent the user name. Supposing that the document has a property holding the user's facebook ID (user_id: FACEBOOK_ID), you can map the document to a channel and give the user access to it. The new Sync Function would look like this:
function(doc, oldDoc) {
channel(doc._id);
access(doc.user_id, doc._id);
}
You can retrieve the user's facebook ID with the Facebook Android SDK and save on a document field.