Problems with Facebook and Google OAuth2

I’m building authentication for a NodeJS app with Realm.

I’ve followed the docs to get Facebook and Google OAuth2 authentication working but Realm is not able to process the tokens properly.

The error message for Facebook is message: 'expected either accessToken, id_token or authCode in payload', code: 47
The error message for Google is message: 'error exchanging access code with OAuth2 provider', code: 47

I have my client IDs and secrets set up correctly so I don’t think this is the issue.
The tokens are I get from Google and Facebook seem correct and I am submitted them as strings to Realm.Credentials and then myRealmApp.logIn.

Can anyone provide advice on what I am missing?

Error messages from Realm logs:

Server code (copy-pasted from Vim; ignore the at the end of the lines): 63 async function loginFacebook(accessToken) { 64 // Log the user in to your app$ 65 console.log(“ACCESS TOKEN”); 66 console.log(accessToken); 67 const credentials = Realm.Credentials.facebook(accessToken); 68 console.log("CREDENTIALS"); 69 console.log(credentials); 70 await realm.logIn(credentials) 71 .then(user => { 72 console.log(`Logged in with id: {user.id});$ 73 }).catch(console.error);$ 74 }$ 75 $ 76 /**$ 77 * Associate a Google login with a Realm user.$ 78 */$ 79 async function loginGoogle(id_token) {$ 80 // Log the user in to your app$ 81 console.log("ID TOKEN");$ 82 console.log(id_token);$ 83 const credentials = Realm.Credentials.google(id_token);$ 84 console.log("CREDENTIALS");$ 85 console.log(credentials);$ 86 await realm.logIn(credentials)$ 87 .then(user => {$ 88 console.log(Logged in with id: {user.id}`); 89 }).catch(console.error); 90 } 91 92 /** 93 * App endpoints$ 94 */ 95 app.post('/login', (req, res) => { 96 provider = req.body.provider; 97 if (! provider) { 98 res.status(400).send(‘No authentication provider provided’); 99 } 100 switch (provider.toLowerCase()) { 101 case 'facebook': 102 accessToken = req.body.accessToken; 103 if (! accessToken) { 104 res.status(400).send(‘Missing access_token’); 105 } 106 return loginFacebook(accessToken).catch(console.error); 107 break; 108 case ‘google’: 109 id_token = req.body.id_token; 110 if (! id_token) { 111 res.status(400).send('Missing id_token'); 112 } 113 return loginGoogle(id_token).catch(console.error);

Requests from login page (the tokens appear to be correctly sent to the back-end):
10 realmFacebookLogin = async function(fbResponse) { 11 accessToken = fbResponse.authResponse.accessToken; 12 await fetch(’/login’, { 13 method: 'POST', 14 headers: { 15 'Content-Type': 'application/json' 16 }, 17 body: JSON.stringify( 18 { 19 accessToken: accessToken, 20 provider: ‘facebook’ 21 } 22 ) 23 }); 24 25 const content = await rawResponse.json(); 26 27 console.log(content); 28 } 29 30 async function realmGoogleLogin(googleUser) { 31 const id_token = googleUser.getAuthResponse().id_token; 32 await fetch(’/login’, { 33 method: 'POST', 34 headers: { 35 'Content-Type': 'application/json' 36 }, 37 body: JSON.stringify( 38 { 39 id_token: id_token, 40 provider: ‘google’ 41 } 42 ) 43 }); 44 }$

In case anyone else runs into this issue, it turns out that this is a problem on the developers’ side.

See: https://github.com/realm/realm-js/issues/3109
https://github.com/realm/realm-js/issues/3116

Hey All - there appears to be hard-breaking change on the Google side. We are investigating a fix now with the Google engineering team

1 Like

Any updates on this at all?

There is a fix for node.js in beta.13 as updated in this issue here - https://github.com/realm/realm-js/issues/3116#issuecomment-696174057

Sorry, I should have been more specific… I’m using the Java version for Android, do we know if there are updates for that?

@Will_Nixon I think the Google OAuth works with the RealmJava SDK today - the problem seems to be in the docs - we erroneously called it googleToken and said access token in our docs whereas what you really need to do is pass in the Google auth code - see docs here:
https://docs.mongodb.com/realm/authentication/google/

And follows this flow:

Let me know if that still doesn’t work for you, we are cleaning up the docs now.

I’ll have a look. At the moment, I don’t see how to get the auth code after receiving the access token - any ideas?

It also doesn’t look like you get a “client secret” when creating credentials in Goodle, only a “client id”, unless I’m meant to manually add a “client secret” to the JSON that’s generated?

Also, this requires us to fetch a user id token but we’re unable to do that without a web server configured, which seems ridiculous. Are there examples y’all have over there for doing this correctly?

Actually @Ian_Ward, it looks like even if I use Google Sign-In and fetch my auth code, I still get an "error exchanging access code with oauth provider.

private fun googleSignIn() {
    val inStream = this::class.java.getResourceAsStream(CREDENTIALS_FILE_PATH)
        ?: throw FileNotFoundException(MyApp.context.getString(R.string.fileNotFoundError))
    val clientSecrets =
        GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inStream))

    val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestServerAuthCode(clientSecrets.web.clientId, true)
        .requestEmail()
        .build()
    mGoogleSignInClient = GoogleSignIn.getClient(MyApp.context, gso)
    val signInIntent = mGoogleSignInClient.signInIntent;
    startActivityForResult(signInIntent, RC_SIGN_IN);
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == RC_SIGN_IN) {
        val task = GoogleSignIn.getSignedInAccountFromIntent(data)
        handleSignInRequest(task)
    }
}

private fun handleSignInRequest(task: Task<GoogleSignInAccount>) {
    try {
        val account = task.getResult(ApiException::class.java)!!
        MyApp.sharedPreferences.edit().putString(getString(R.string.preferences_authCode), account.serverAuthCode)
        realmSignIn(account.serverAuthCode!!)
    } catch (error: ApiException) {
        println("Sign-in error: ${error.statusCode}")
        Log.d(SIGN_IN_TAG,"Sign-in error: ${error.statusCode}")
    }
}

private fun realmSignIn(authCode: String) {
    val credential = Credentials.google(authCode)
    MyApp.realmApp.loginAsync(credential) {
        if (it.isSuccess) {
            myHandler.postDelayed({
                goToMainActivity()
            }, splashTime)
        } else {
            println("Error logging in: ${it.error}")
            Log.d(SIGN_IN_TAG,"Error logging in: ${it.error}")
        }
    }
}

@Will_Nixon Are you able to email at ian.ward@monogodb.com with your Realm Dashboard URL? I’d like to take a look from our side

Hi Ian,

I’ve cracked it! The problem is that you have to create a Web Client in Google as well as your Android client - no one seems to be massively clear about this, only that Google Sign-In creates one automatically (it hadn’t - I had to try and generate one from the Google Sign-In docs, although it wasn’t working and then suddenly there it was!).

With that Web Client, I could then use that Client ID inside the Android app to make calls to get the auth code to pass to MongoDB/Realm.

The final problem was that the Mongo docs are very unclear about this - you ask for a Client ID and Client secret, but it’s the WEB CLIENT id and secret you need, not the Android client id (which has no secret). That’d be really helpful to make clear in your docs and on the Realm Dashboard. I only figured it out by trying it just now before sending you the link!

@Will_Nixon Thanks for the feedback and I’m glad you were able to get it working. Generally I’d prefer to leave the docs to the 3rd party provider (Google) since we should just be a wrapper around their implementation but it sounds like there are enough issues with this logIn that we should provide them as well. I’ll look to get that sorted - it’s interesting that we don’t have these kinds of problems with our other built-in providers like Facebook and Apple.