Skip to content

Instantly share code, notes, and snippets.

@lukasvan3l
Last active August 5, 2022 07:50
Show Gist options
  • Save lukasvan3l/355a4fc1968c5f438b85 to your computer and use it in GitHub Desktop.
Save lukasvan3l/355a4fc1968c5f438b85 to your computer and use it in GitHub Desktop.
Any OAuth login service ever created, added to your app within 5 minutes

Adding any OAuth login to your Meteor app

tl;dr: Copy the q42:accounts-microsoft and q42:microsoft package, modify the URLs and service name and it works! Probably anyway...

When creating a commercial application with a wide target audience, you can stumble upon users wanting to login using an unknown and barely used OAuth service. And why deny your users this when you can easily create a login service package?

With only about 400 million active users and only 2nd place as world most valuable brand it makes sense that no developer has created an implementation to log in with your Microsoft account. But believe it or not, some people like logging in with their Microsoft Account. And so, by building on the the default meteor google and accounts-google packages, the q42:microsoft and q42:accounts-microsoft packages came to be!

Microsoft offers extensive documentation on the subject and implemented the OAuth2 service completely according to the official specifications. Because of this, it was almost enough to change only the entry points in the Google and Accounts-Google packages to get this to work! So I started digging, why was this so incredibly easy to create?

As it turns out, Meteor has got some amazing methods you can call. Although not documented anywhere, these methods allow entry points to be specified and results to be mapped for any OAuth capable service. And they include all the security and configuration features you would want. So let’s dive into this magical world together.

Creating the OAuth package

(the “hard” part)

Basically, just like all other accounts packages before this one, I wanted to separate the OAuth flow from the accounts flow. This way, the data made available by OAuth for the service you need, is available to other packages that might want this data.

Make sure your package extends upon the existing oauth and oauth2 packages. This is not the place to explain how to use api.use, so you can figure that out yourself. Furthermore, you will need some sort of http package to create requests to the OAuth server.

The only thing you need to do on the server is registering your service with meteor OAuth. you can do this by calling the method OAuth.registerService. The most important part in all of this is that the callback function returns an object containing the required servicedata.

Here's a very basic example using an OAuth2 service:

// Parameters: ServiceName, Oauth version, Oauth1 urls, callback
// The query returned usually contains the code and state returned by the service
OAuth.registerService('someService', 2, null, function(query) {

    // Non working example data fetching, usually to get stuff from an OAuth service, you
    // need a clientId and client secret of sorts.
    var response =  HTTP.post('https://getting-some.token.for/oauth')
        identity =  HTTP.post('https://getting-user-data.from.the/same-service');
        
    return {
        
        serviceData: serviceData = {
            accessToken: response.accessToken,
            refreshToken: response.refreshToken,
            idToken: response.idToken,
            expiresAt: (+new Date) + (1000 * parseInt(response.expiresAt, 10)),
            scope: response.scope
        },
        options: {profile: {name: identity.name}}
    };
});

Now that it's registered, you open up your world to an extensive set of OAuth features! On the server you can now call retrieveCredential() which will take care of all the nasy OAuth stuff. It's a good idea to expose this method for other packages to use. Which can be easily done this way:

SomeService = {
    retrieveCredential: function(credentialToken, credentialSecret) {
        return OAuth.retrieveCredential(credentialToken, credentialSecret);
    }
}

On the client you can now open up a popup that utilizes the registered service.

var yourServiceName = 'someService', // Make sure this is the name you registered
    credentialToken = Random.secret(); // Or you can create your own unique key!

OAuth.launchLogin({
    loginService: yourServiceName,
    loginStyle: OAuth._loginStyle(yourServiceName, configObject, optionsObject),
    loginUrl: "https://the-url-to-open.inside.the/popup?clientId=something&secret=somethingelse", // The entry url to the login page of the service
    credentialRequestCompleteCallback: callback,
    credentialToken: credentialToken,
    popupOptions: { width: 800, height: 600 } // (Optional) Dimensions for the popup window
});

The OAuth._loginStyle takes the value of the key 'loginStyle' from the optionsObject or configObject, in that order. This value can either be 'popup' or 'redirect' as can be found here. The configObject is usually the ServiceConfiguration and the optionsObject is the inserted object in your own function.

And that's basically all you need. Although you do have some more utility methods to your disposal!

Getting the service configuration

ServiceConfiguration.configurations.findOne({service: yourServiceName});

Generating a redirect URI for your OAuth URL

OAuth._redirectUri(yourServiceName, config)

Generating an OAuth2 state parameter

OAuth._stateParam(loginStyle, credentialToken, redirectUrl)

Creating the Accounts package

(wait that's it?)

So, this package actually doesn't do that much. It only creates an easy to use method on the client side for summoning the login popup. All the logic for actually popping it up is inside the OAuth package. So this is basically it:

// Register your service
Accounts.oauth.registerService('someService');

if (Meteor.isClient) {
    Meteor.loginWithSomeService = function(options, callback) {
        // Get the complete handler and insert the client side callback
        var onComplete = Accounts.oauth.credentialRequestCompleteHandler(callback);
        // Activate your home-made requestCredential
        SomeService.requestCredential(options, onComplete);
    }
}

You can also unregister your service the same way, should you need to.

// Unregister your service, should you ever need to...
Accounts.oauth.unregisterService('someService');

But wait, there's more!

Meteor created an easy to use Accounts.addAutopublishFields method to speed up your development. If the autopublish meteor package is installed, these fields are automatically published to the client from your service! It can be used on the server as follows:

Accounts.addAutopublishFields({
    forLoggedInUser: [services.someService.username, services.someService.someSecretField],
    forOtherUsers: [services.someService.username]
});

And there you have it, great features tucked away in the awesomeness that is Meteor. Of course, if you decide to create a new OAuth service package, share it with the world by publishing it!

Now,... let the explosive creation of accounts packages commence!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment