SSO with Firebase

Is it possible to use Firebase as the SSO for Discourse? I’ve tried several times to get it working, but to no avail.

Has anyone been successful in using Firebase with Discourse?

3 Likes

Can you share your code?

There’s no real code yet. I configured Discourse with the sso secret and url, but it didn’t work. I don’t really know what the url should be. Thanks the issue.

Also curious about how to do this.

Bump. Is this possible?

Did anyone figure out, how to use authentication from firebase for discourse?

Nobody found anyway to do it that I know of. I stopped looking when I didn’t get any real help. But I did figure out an alternative. All authentication is done through Firebase. If a user forgets a password or wants to change it, it is handled by Firebase. Then, if a user is able to authenticate through Firebase, I created an account in Discourse with the same user name, but with a global, secure password. Worked for my solution. You can check out the app I made for a client on Android. Search for WWJDNow in the Android Play Store. I haven’t had to do anything similar for another client yet, so I didn’t look into if further and don’t have any other example. Hope that helps.

I wanted to bump this because I’m running into the issue as well. Have there been any updates or has anyone figured it out? Otherwise I’m unsure how to pass a users Firebase info to the server for SSO.

As a follow up to myself and pinging @adam_beers and @vinothkannans, I’ve been doing a lot of research today and it seems like it may be possible using Firebase’s new ability to create and then check session cookies.

I think you would be able to store a user’s session login as a cookie when they initially login (and redirect to your main login page when they don’t have a session), and then verify that cookie inside your SSO handling if it’s passes from the request. If the cookie is verified, you could then extract the DecodedIdToken and properly send back a response for logging in.

Any thoughts on this method?

I got it working!

Hey everyone! Extremely happy to report I’ve gotten Firebase working as an SSO option for Discourse. It was as I had suggested in my last post that the key to making this work was to leverage the new ability for the Firebase SDK to set cookies on the browser after a login session.

On client-side you’ll need to do something like this:

auth.setPersistence(firebase.auth.Auth.Persistence.SESSION);

Which first sets the persistence state of your Firebase session.

Then you’ll need to tie into your users login flow with Firebase’s token logic. The implementation on our site is like this:

auth.signInWithEmailAndPassword(email, password).then(function(credential) {
    credential.user.getIdToken(true).then(function(token) {
    var FD = new FormData();
    FD.append("idToken", token);
    $.ajax({
        url: 'ENDPOINTURL',
        xhrFields: { withCredentials: true },
        data: FD,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST'
    })
    .done(function(data) {
         // redirect to wherever you want a logged in user to go to
          location.href = "/account";
    })
    .fail(function(xhr) {
         log('error', xhr);
    });
});

This hits an endpoint on our (node/express) server, which looks like this:

app.post('ENDPOINTURL',formidable(),function(req,res) {
  // Get the ID token passed
  var idToken = req.fields.idToken;
  // Set session expiration to 5 days.
  var expiresIn = 60 * 60 * 24 * 5 * 1000;
  // Create the session cookie. This will also verify the ID token in the process.
  // The session cookie will have the same claims as the ID token.
  // We could also choose to enforce that the ID token auth_time is recent.
  auth.verifyIdToken(idToken).then(function(decodedClaims) {
      // In this case, we are enforcing that the user signed in in the last 5 minutes.
      if (new Date().getTime() / 1000 - decodedClaims.auth_time < 5 * 60) {
      return auth.createSessionCookie(idToken, {expiresIn: expiresIn});
    }
       throw new Error('UNAUTHORIZED REQUEST!');
  })
  .then(function(sessionCookie) {
        // Note httpOnly cookie will not be accessible from javascript.
        // secure flag should be set to true in production.
        var options = {maxAge: expiresIn, domain: 'depthkit.tv', httpOnly: false, secure: true /** false to test in localhost */};
        res.cookie('session', sessionCookie, options);
        res.end(JSON.stringify({status: 'success'}));
  })
  .catch(function(error) {
        res.status(401).send('UNAUTHORIZED REQUEST!');
  });
});

Note that this is mostly a reference implementation from Google’s sample here. Also, auth is our firebase admin account Auth object.

This sets the session cookie in the client browser. It’s worth noting here as well that we are using subdomains, so the intended and implemented ability is that you can login/register at depthkit.tv and then login to our forums at a subdomain using SSO.

Lastly, Discourse has to ping our server for SSO auth (triggered on clicking Login), which looks like this.

app.get('SSOENDPOINT', cookieParser(), function(req,res) {
    const sessionCookie = req.cookies.session || '';
    // Verify the session cookie. In this case an additional check is added to detect
    // if the user's Firebase session was revoked, user deleted/disabled, etc.
    auth.verifySessionCookie(sessionCookie, true /** checkRevoked */)
        .then((decodedClaims) => {
        //once we are here the user cookie is known to be valid and we can extract the uid
        var uid = decodedClaims.uid;
        var payload = req.query.sso; // fetch from incoming request
        var sig = req.query.sig; // fetch from incoming request
        if(sso.validate(payload, sig)) 
        {
            //valid sso, make sure the user is valid
            auth.getUser(uid)
            .then(function(userRecord) {
                // Successfully fetched user data
                var nonce = sso.getNonce(payload);
                var userparams = {
                // Required, will throw exception otherwise
                "nonce": nonce,
                "external_id": uid,
                "email": userRecord.email,
                // Optional - could pull these from DB values
                "username": userRecord.displayName,
                "name": userRecord.displayName
                };
                var q = sso.buildLoginString(userparams);
                console.log("user authed and signed in through SSO, redirecting");
                res.redirect('http://FORUMURL/session/sso_login?' + q);
            })
            .catch(function(error) {
                console.log("Error fetching user data:", error);
                console.log("redirecting to login because of invalid user data");
                res.redirect('https://www.depthkit.tv/login');
            });
        }
        else
        {
            console.log("unable to validate discourse payload, redirecting to login");
            res.redirect('https://www.depthkit.tv/login');
        }
        }).catch(error => {
        // Session cookie is unavailable or invalid. Force user to login.
        console.log("invalied session cookie, redirecting to login");
        res.redirect('https://www.depthkit.tv/login');
    });
});

This is also largely pulled from Firebase’s sample implementation as well as the reference code for the discourse-sso node (sso in the code) package (thank you to the creator!!).

Learned a lot about working with the web in general when implementing this, so hopefully other people can find it useful as well!

12 Likes

Just wanted to thank you for sharing your code! Worked great!

Some related things in case anyone else walks down this road:

  • be sure to add your domain to Authorized domains in Firebase Console (Authentication -> Sign-in Method)

  • pay careful attention to this comment in the code above, during testing it might catch you off guard // In this case, we are enforcing that the user signed in in the last 5 minutes.

  • if you need to use CORS to access from a different domain you can use multiple middlewares by passing in an array: [cors(corsOptions),formidable()]

Thanks again for sharing!

4 Likes

I followed your code and have implemented it in cloud functions but for the discourse setup what would be the sso url

That’s the SSO route on your server, above I have it as ‘SSOENDPOINT’, but in reality it may be something like ‘/auth/v1/sso’. For cloud functions I’m not sure, but it would probably be the URL of your cloud function.

To enable in discourse admin you only need to update 3 options:

“enable sso” - checked
“sso url” - url to the /sso/discourse firebase function endpoint
“sso secret” - your-super-secret-secret (change this)

@depthkit thx for posting I was able to implement this as well.

I posted the full code here:

https://gist.github.com/tegument/f60ef0bafac80faf7522f3a29fa04435

4 Likes

This works perfectly in web, but in mobile web the session cookie is not set properly and it comes out empty in the discourse sso url request. Has this happened to anyone else? Any clues what it could be?

Bumping this since a lot of our users look at discussions on their phone. Is it working for mobile web for everyone else?

Hi all,

I have implemented the linked code - from the github and from the comment. I have worked through most errors (CORS was an issue).

I can now log in through my own Firebase Auth, however the session cookie is not passed on through app.get(/discourse,…). I have console.logged the debug statements and the sessionCookie is set to blank. When I console.log the req at that point there is no “cookies” of any sort in the req.

Does anybody have any idea what might be causing this or has already solved it?

Many thanks

I know this topic is somewhat old, but I have a suggestion

Create a NodeJS application and publish it to Firebase Hosting.
Exists a discourse-sso module for NodeJS, this “app” could be an intermediary to facilitate the Firebase Auth SSO

I will try to create something similar and post a tutorial here on Meta

3 Likes

This would be extremely helpful!

Would be great to have a SSO Plugin for Firebase as there is for other platforms already!

5 Likes