Authenticating with Cloud Functions for Firebase from your iOS Swift app

My "blog" seems to be a random collection of things that I want people to be able to find. So be it!

Right now (August 2017) I can't find good documentation about how take an iOS app in which the user is logged into Firebase, and use those credentials to communicate with a Cloud Function for Firebase. Here's how, inspired partly by the authorized-https-endpoint example function, and partly by a helpful post by Kato Richardson.

Unfortunately this method doesn't work to access the database directly, as in Kato's post. The "authorization" header set below doesn't appear to be read for that.

Nobody should copy/paste this code and use it unmodified. It handles errors poorly.

    static func getJson(url: URL,
          completion callback: ((_ json: [String: Any]?, _ error: String?) -> Void)?) {
      Auth.auth().currentUser!.getIDToken(completion: { (token, error) in
        if let error = error {
          callback?(nil, "ID token retrieval error: \(error.localizedDescription)")
          return
        }
        var request = URLRequest(url: url)
        request.setValue("Bearer \(token!)", forHTTPHeaderField: "authorization")
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
          if let error = error {
            callback?(nil, "HTTP error: \(error.localizedDescription)")
            return
          }
          if let response = response as? HTTPURLResponse {
            if response.statusCode >= 400 && response.statusCode < 600 {
              callback?(nil, "HTTP status code \(response.statusCode)")
              return
            }
          }
          if let data = data {
            do {
              let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String: Any]
              callback?(json, nil)
              return
            } catch let jsonError {
              callback?(nil, "JSON error: \(jsonError.localizedDescription)")
              return
            }
          } else {
            callback?(nil, "No JSON found.")
            return
          }
        })
        task.resume()
      })

    }

In the Cloud Function:

exports.testAuth = functions.https.onRequest((req, res) => {
    return extractFirebaseUser(req).then(user => {
        if (user === null) {
            res.status(403).json({message: 'Forbidden'});
        }
        res.status(200).json(user);  // Or whatever
    });

});

Which calls this function:

// Returns a promise resolving to a Firebase user, or to null if one was not successfully extracted.
exports.extractFirebaseUser = function extractFirebaseUser(req) {
    return Promise.resolve(true).then(() => {
        // Check if request is authorized with Firebase ID token
        if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
            !(req.cookies && !req.cookies.__session)) {
            console.log('No Firebase ID token was passed as a Bearer token in the Authorization header.',
                'Make sure you authorize your request by providing the following HTTP header:',
                'Authorization: Bearer <Firebase ID Token>',
                'or by passing a "__session" cookie.');
            return null;
        }

        let idToken;
        if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
            console.log('Found "Authorization" header');
            // Read the ID Token from the Authorization header.
            idToken = req.headers.authorization.split('Bearer ')[1];
        } else {
            console.log('Found "__session" cookie');
            // Read the ID Token from cookie.
            idToken = req.cookies.__session;
        }
        return admin.auth().verifyIdToken(idToken).then(decodedIdToken => {
            console.log('ID Token correctly decoded', decodedIdToken);
            return decodedIdToken;
        }).catch(error => {
            console.log('Error while verifying Firebase ID token:', error);
            return null;
        });
    });

};

I hope someone finds that useful. Please leave a comment if you did!

Comments

Popular posts from this blog

No, programming competitions don't produce bad engineers

Authenticating phone numbers with Firebase on iOS