This is a second part in the series of articles demonstrating the social login functionality in Baasic. In case you haven’t read the first part which shows how to set up social login providers, you can find it here. This time we will be building upon the Baasic Membership SDK article.

How it all works?

According to Wikipedia OAuth can be defined as: … an open standard for authorization. OAuth provides client applications a ‘secure delegated access’ to server resources on behalf of a resource owner. It specifies a process for resource owners to authorize third-party access to their server resources without sharing their credentials.

OAuth has several modes which are called grant types: - Authorization Code - Implicit - Password - Client credentials

We will be focusing on the Authorization Code grant type.

How it works in general is fairly simple. An anonymous user wishes to log into your cool new app, but he or she doesn’t have an account and instead wants to use a third-party provider such as Facebook or Twitter. User then clicks on a social login button and is redirected to the third-party provider where he is prompted to enter credentials (if the user was not previously logged in). After this process is completed, the provider will redirect the user back to your app. After that, a web server makes a request to the provider’s API with the Authorization Code it got; in exchange, it will be given an access token which can be used to access non-public, protected resources.

Since Baasic implements an API which is using the Authorization Code grant type, it handles most of the steps described above. The general idea behind the implementation is the following: 1. We make an API call to the Baasic API and ask for a redirect URL to a third-party provider. 2. API returns a login URL for the provider. This can differ from provider to provider and is handled differently by OAuth1.0a and OAuth2. 3. In our app code we redirect the user to the URL obtained via the API call. 4. User gives consent to the app request. 5. User gets redirected back to our app and we parse the query parameters to get the authorization code. 6. We make a POST request to the Baasic API containing the authorization code. Baasic takes over and attempts to authenticate the user. If all goes well, we will receive the access token.

Social login in action

Before we proceed, please read the post about Baasic Membership SDK, as it described the basic concepts that will be used in the example app. This article will also build upon the Membership SDK example mentioned there.

The workflow described in the previous section poses a small problem if we are using multiple login providers. Since all state information will be lost when we redirect a user to the selected provider, we want to make sure that a certain information - such as which provider the user selected - is preserved. The solution to this problem is to simply use the browser local storage. There are also alternative methods to handle this - such as opening a new child window - however the solution we’ve opted for in this demo is simple enough. So to cover this, we will use these two small helper methods:

    var storageKey = 'socialData',
    // Reads stored social login data in local storage
    var getStoredSocialLoginData = function() {
        var data = localStorage.getItem(storageKey);
        if (data){
            return JSON.parse(data);
        }
    };
    // Sets social login data in local storage.
    var storeSocialLoginData = function(data){
        if (!data){
            localStorage.setItem(storageKey, null);
        } else {
            localStorage.setItem(storageKey, JSON.stringify(data));
        }
    };  

Now we need to set up our buttons, so in the login.html we’ve added the following 4 buttons:

    <h2>Social auth</h2>            
    <div>
        <button type="button" ng-click="vm.socialLogin.startLogin('facebook')" title="Sign in with Facebook">Facebook</button>
        <button type="button" ng-click="vm.socialLogin.startLogin('twitter')" title="Sign in with Twitter">Twitter</button>
        <button type="button" ng-click="vm.socialLogin.startLogin('google')" title="Sign in with Google">Google</button>
        <button type="button" ng-click="vm.socialLogin.startLogin('github')" title="Sign in with Github">Github</button>
    </div>     

When any of these 4 buttons is clicked, we will perform a call to the Baasic API to obtain a valid redirect URL based on the selected provider. We will then store the provider information and finally perform a redirect to the provider. Here is how the code looks:

    vm.socialLogin.startLogin = function(provider){
        // This call will build us a provider login url which we can then use to redirect the user to an external website.
        baasicLoginService.social.get(provider, returnUrl)
            .success(function (data) {
                // We are remembering the response and provider and are storing some additional data in the local storage.
                data.provider = provider;                           
                data.activationUrl = activationUrl;
                storeSocialLoginData(data);
                window.location.href = data.redirectUri;                
            })
            .error(function (data, status) {
                var text = 'An error has occurred while fetching login social login parameters.';
                if (data.error){
                    text = data.error_description;
                }               
                vm.socialLogin.notification = text;
            });
    };  

The next step is to handle redirects back from a provider and, if possible, automatically authenticate the user. In the previous step we’ve told the provider via returnUrl to return us back to the same page we started at, in this case it is the login page. So first we need to detect if a user is visiting a page for the first time or has been redirected to the login page by the social login provider. We can tell if the redirect came from a social login provider by the presence of either: - oauth_token and oauth_verifier query parameters for OAuth1.0a providers - code query parameter for OAuth2.0 providers

An additional check can be made in the local storage to see if the stored provider data is available. So by combining all of this, we end up with the following code:

    // Response data from the server is stored in local storage since the login process requires that a user leaves the site and be redirected to the social login provider website. There are various ways to implement this, this can also be handled by opening a separate window; an implementation which is done on dashboard.baasic.com.
    vm.socialLogin.providerData = getStoredSocialLoginData();
    if (vm.socialLogin.providerData && !vm.isUserLoggedIn){
        // If we have data stored in local storage this means that the user has been returned here via callback from the social login provider website so in order to lock the form we're parsing the response data and verifying that the required response codes are present.
        var responseData = baasicLoginService.social.parseResponse(vm.socialLogin.providerData.provider, returnUrl);
        if (responseData.code || responseData.oAuthToken){
            vm.socialLogin.inProgress = true;
            // Automatically we're also attempting to login
            angular.extend(vm.socialLogin.providerData, responseData);
            vm.socialLogin.login();
        } else {
            // The local storage data exists but we have not detected a valid redirect from the social login provider. So we do some garbage cleaning here and clear out the localstorage and indicate that social login is not in progress.
            storeSocialLoginData();
            vm.socialLogin.inProgress = false;
        }
    }   

If during this process we determine that we have all of the required information, we call the login method which performs a call to the Baasic API. If all goes as planned, a Baasic token is returned to us. Please note that this check is performed each time the login controller is initialized, to allow us to handle redirect scenarios from social login providers.

The next step is to demonstrate how to obtain the Baasic token from the API. But before we proceed we need to cover some basics. Since we are using multiple providers, we need to link the information returned from them with the correct Baasic account - the only way to do it is by using the e-mail address. Some providers like Twitter do not expose this, while other (Facebook) allow users to deny sharing e-mail information with external apps. In these circumstances Baasic will return a BadRequest with the missing_email error code.

In scenarios where we have a user in the system created via regular sign-in process, a conflict can be detected when a social login attempt is being made with the same e-mail address. The system will return an error message with the invalid_grant error code. In this scenario we must ask the user to provide the existing account password and e-mail to confirm the identity.

Now, having explained a few of such “borderline” cases, we need to expand the example login.html with additional fields to cover for them:

    <div ng-show="vm.socialLogin.showCredentials">
        <form name="socialLoginForm">
            <h2>social login Form</h2>
            <div class="field">
                <label for="social-login-userName" class="label">Username:</label>
                <input id="social-login-userName" class="input" type="email" ng-model="vm.socialLogin.model.email" required maxlength="30">
            </div>
            <div class="field">
                <label for="password" class="label">Password:</label>
                <input id="password" class="input" type="password" ng-model="vm.socialLogin.model.password" required maxlength="30">
            </div>
            <div class="field">
                <button type="submit" class="button" ng-click="vm.socialLogin.login()">Submit</button>
                <button type="submit" class="button" ng-click="vm.socialLogin.cancel()" >Cancel</button>
            </div>
        </form>
    </div>

This form will only be revealed in case one of the described scenarios is detected in the login method. Now, after we’ve added the form, we can move to the login method, here is the code:

    vm.socialLogin.login = function(){
        // Clone provider data and add email and password to the request if available
        var data = angular.copy(vm.socialLogin.providerData);
        if (vm.socialLogin.model.email){
            data.email = vm.socialLogin.model.email;
        }
        if (vm.socialLogin.model.password){
            data.password = vm.socialLogin.model.password;
        }       
        if (vm.socialLogin.sendingData){
            return;
        }
        vm.socialLogin.sendingData = true;
        // We are now in the position to attempt to login the user, system will perform code exchange to obtain a token and will check if the user exists in the system.
        baasicLoginService.social.post(data.provider, data)
            .success(function (data) {              
                vm.isUserLoggedIn = true;
                vm.message = 'Successful login';  
                vm.socialLogin.inProgress = false;
                vm.socialLogin.showCredentials = false;
                vm.socialLogin.notification = '';               
            })
            .error(function (data, status) {
                var text = 'Could not login user into the system';
                if (data.error){
                    text = data.error_description;
                    if (data.error === 'invalid_grant' || data.error === 'missing_email'){
                        // Existing user detected in the system, possibly data provided without the user password as well so prompt the user to provide the email and password
                        vm.socialLogin.showCredentials = true;
                        text = 'Account already exists please enter your account credentials.';
                    }                                           
                }
                vm.socialLogin.notification = text;
            }).finally(function(){
                vm.socialLogin.sendingData = false;
            });
    };

As you can see, the majority of what we’ve just talked about is covered by just a few lines of code.

Conclusion

This post demonstrates how to use Baasic social login infrastructure in your apps. Future articles will cover more advanced scenarios, such as handling users with multiple social network accounts. The source code for this article is available at GitHub under feature branch called social-login.

Feel free to leave a comment

comments powered by Disqus