Blog Archives

Authenticate OpenUI5 applications and Lumen backends with Amazon Cognito and JWT

Today I want to create an UI5/OpenUI5 boilerplate that plays with Lumen backends. Simple, isn’t it? We only need to create a Lumen API server and connect our OpenUI5 application with this API server. But today I also want to create a Login also. The typical user/password input form. I don’t want to build it from scratch (a user database, oauth provider or something like that). Since this days I’m involved with Amazon AWS projects I want to try Amazon Cognito.

Cognito has a great javaScript SDK. In fact we can do all the authentication flow (create users, validate passwords, change password, multifactor authentication, …) with Cognito. To create this project first I’ve create the following steps within Amazon AWS Cognito Console: Create a user pool with required attributes (email only in this example), without MFA and only allow administrators to create users. I’ve also created a App client inside this pool, so I’ve got a UserPoolId and a ClientId.

Let’s start with the OpenUI5 application. I’ve created an small application with one route called “home”. To handle the login process I will work in Component.js init function. The idea is check the cognito session. If there’s an active one (that’s means a Json Web Token stored in the local storage) we’ll display to “home” route and if there isn’t we’ll show login one.

sap.ui.define([
        "sap/ui/core/UIComponent",
        "sap/ui/Device",
        "app/model/models",
        "app/model/cognito"
    ], function (UIComponent, Device, models, cognito) {
        "use strict";

        return UIComponent.extend("app.Component", {

            metadata: {
                manifest: "json"
            },

            init: function () {
                UIComponent.prototype.init.apply(this, arguments);
                this.setModel(models.createDeviceModel(), "device");
                this.getRouter().initialize();

                var targets = this.getTargets();
                cognito.hasSession(function (err) {
                    if (err) {
                        targets.display("login");
                        return;
                    }
                    targets.display("home");
                });
            },

            /* *** */
        });
    }
);

To encapsulate the cognito operations I’ve create a model called cognito.js. It’s not perfect, but it allows me to abstract cognito stuff in the OpenUI5 application.

sap.ui.define([
        "app/conf/env"
    ], function (env) {
        "use strict";

        AWSCognito.config.region = env.region;

        var poolData = {
            UserPoolId: env.UserPoolId,
            ClientId: env.ClientId
        };

        var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
        var jwt;

        var cognito = {
            getJwt: function () {
                return jwt;
            },

            hasSession: function (cbk) {
                var cognitoUser = cognito.getCurrentUser();
                if (cognitoUser != null) {
                    cognitoUser.getSession(function (err, session) {
                        if (err) {
                            cbk(err);
                            return;
                        }
                        if (session.isValid()) {
                            jwt = session.idToken.getJwtToken();
                            cbk(false, session)
                        } else {
                            cbk(true);
                        }
                    });
                } else {
                    cbk(true);
                }
            },

            getCurrentUser: function () {
                return userPool.getCurrentUser();
            },

            signOut: function () {
                var currentUser = cognito.getCurrentUser();
                if (currentUser) {
                    currentUser.signOut()
                }
            },

            getUsername: function () {
                var currentUser = cognito.getCurrentUser();
                return (currentUser) ? currentUser.username : undefined;
            },

            getUserData: function (user) {
                return {
                    Username: user,
                    Pool: userPool
                };
            },

            getCognitoUser: function (user) {
                return new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(cognito.getUserData(user));
            },

            authenticateUser: function (user, pass, cbk) {
                var authenticationData = {
                    Username: user,
                    Password: pass
                };

                var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
                var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(cognito.getUserData(user));

                cognitoUser.authenticateUser(authenticationDetails, cbk);

                return cognitoUser;
            }
        };

        return cognito;
    }
);

The login route has the following xml view:

<core:View
        xmlns:core="sap.ui.core"
        xmlns:f="sap.ui.layout.form"
        xmlns="sap.m"
        controllerName="app.controller.Login"
>
    <Image class="bg"></Image>
    <VBox class="sapUiSmallMargin loginForm">
        <f:SimpleForm visible="{= ${/flow} === 'login' }">
            <f:toolbar>
                <Toolbar>
                    <Title text="{i18n>Login_Title}" level="H4" titleStyle="H4"/>
                </Toolbar>
            </f:toolbar>
            <f:content>
                <Label text="{i18n>Login_user}"/>
                <Input placeholder="{i18n>Login_userPlaceholder}" value="{/user}"/>
                <Label text="{i18n>Login_pass}"/>
                <Input type="Password" placeholder="{i18n>Login_passPlaceholder}" value="{/pass}"/>
                <Button type="Accept" text="{i18n>OK}" press="loginPressHandle"/>
            </f:content>
        </f:SimpleForm>
        
        <f:SimpleForm visible="{= ${/flow} === 'PasswordReset' }">
            <f:toolbar>
                <Toolbar>
                    <Title text="{i18n>Login_PasswordReset}" level="H4" titleStyle="H4"/>
                </Toolbar>
            </f:toolbar>
            <f:content>
                <Label text="{i18n>Login_verificationCode}"/>
                <Input type="Number" placeholder="{i18n>Login_verificationCodePlaceholder}" value="{/verificationCode}"/>
                <Label text="{i18n>Login_newpass}"/>
                <Input type="Password" placeholder="{i18n>Login_newpassPlaceholder}" value="{/newPass}"/>
                <Button type="Accept" text="{i18n>OK}" press="newPassVerificationPressHandle"/>
            </f:content>
        </f:SimpleForm>
        
        <f:SimpleForm visible="{= ${/flow} === 'newPasswordRequired' }">
            <f:toolbar>
                <Toolbar>
                    <Title text="{i18n>Login_PasswordReset}" level="H4" titleStyle="H4"/>
                </Toolbar>
            </f:toolbar>
            <f:content>
                <Label text="{i18n>Login_newpass}"/>
                <Input type="Password" placeholder="{i18n>Login_newpassPlaceholder}" value="{/newPass}"/>
                <Button type="Accept" text="{i18n>OK}" press="newPassPressHandle"/>
            </f:content>
        </f:SimpleForm>
    </VBox>
</core:View>

It has three different stages: “login”, “PasswordReset” and “newPasswordRequired”
“login” is the main one. In this stage the user can input his login credentials. If credentials are OK then we’ll display home route.
The first time a user log in in the application with the password provided by the administrator, Cognito will force to change the password. Then We’ll show newPasswordRequired flow. I’m not going to explain each step. We developers prefer code than texts. That’s the code:

sap.ui.define([
        "app/controller/BaseController",
        "sap/ui/model/json/JSONModel",
        "sap/m/MessageToast",
        "app/model/cognito"
    ], function (BaseController, JSONModel, MessageToast, cognito) {
        "use strict";

        var cognitoUser;
        return BaseController.extend("app.controller.Login", {
            model: {
                user: "",
                pass: "",
                flow: "login",
                verificationCode: undefined,
                newPass: undefined
            },

            onInit: function () {
                this.getView().setModel(new JSONModel(this.model));
            },

            newPassPressHandle: function () {
                var that = this;
                var targets = this.getOwnerComponent().getTargets();
                var attributesData = {};
                sap.ui.core.BusyIndicator.show();
                cognitoUser.completeNewPasswordChallenge(this.model.newPass, attributesData, {
                    onFailure: function (err) {
                        sap.ui.core.BusyIndicator.hide();
                        MessageToast.show(err.message);
                    },
                    onSuccess: function (data) {
                        sap.ui.core.BusyIndicator.hide();
                        that.getModel().setProperty("/flow", "login");
                        targets.display("home");
                    }
                })
            },

            newPassVerificationPressHandle: function () {
                var that = this;
                var targets = this.getOwnerComponent().getTargets();
                sap.ui.core.BusyIndicator.show();
                cognito.getCognitoUser(this.model.user).confirmPassword(this.model.verificationCode, this.model.newPass, {
                    onFailure: function (err) {
                        sap.ui.core.BusyIndicator.hide();
                        MessageToast.show(err);
                    },
                    onSuccess: function (result) {
                        sap.ui.core.BusyIndicator.hide();
                        that.getModel().setProperty("/flow", "PasswordReset");
                        targets.display("home");
                    }
                });
            },

            loginPressHandle: function () {
                var that = this;
                var targets = this.getOwnerComponent().getTargets();
                sap.ui.core.BusyIndicator.show();
                cognitoUser = cognito.authenticateUser(this.model.user, this.model.pass, {
                    onSuccess: function (result) {
                        sap.ui.core.BusyIndicator.hide();
                        targets.display("home");
                    },

                    onFailure: function (err) {
                        sap.ui.core.BusyIndicator.hide();
                        switch (err.code) {
                            case "PasswordResetRequiredException":
                                that.getModel().setProperty("/flow", "PasswordReset");
                                break;
                            default:
                                MessageToast.show(err.message);
                        }
                    },

                    newPasswordRequired: function (userAttributes, requiredAttributes) {
                        sap.ui.core.BusyIndicator.hide();
                        that.getModel().setProperty("/flow", "newPasswordRequired");
                    }
                });
            }
        });
    }
);

The home route is the main one. It asumes that there’s an active Cognito session enabled.

<mvc:View
        controllerName="app.controller.Home"
        xmlns="sap.m"
        xmlns:mvc="sap.ui.core.mvc"
        xmlns:semantic="sap.m.semantic">
    <semantic:FullscreenPage
            id="page"
            semanticRuleSet="Optimized"
            showNavButton="false"
            title="{i18n>loggedUser}: {/userName}">
        <semantic:content>
            <Panel width="auto" class="sapUiResponsiveMargin" accessibleRole="Region">
                <headerToolbar>
                    <Toolbar height="3rem">
                        <Title text="Title"/>
                    </Toolbar>
                </headerToolbar>
                <content>
                    <Text text="Lorem ipsum dolor st amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat"/>
                    <Button text="{i18n>Hello}" icon="sap-icon://hello-world" press="helloPress"/>
                </content>
            </Panel>
        </semantic:content>
        <semantic:customFooterContent>
            <Button text="{i18n>LogOff}" icon="sap-icon://visits" press="onLogOffPress"/>
        </semantic:customFooterContent>
    </semantic:FullscreenPage>
</mvc:View>

It shows the Cognito login name. It alos has a simple logff button and also one button that calls to the backend.

sap.ui.define([
        "app/controller/BaseController",
        "sap/ui/model/json/JSONModel",
        "sap/m/MessageToast",
        "app/model/cognito",
        "app/model/api"
    ], function (BaseController, JSONModel, MessageToast, cognito, api) {
        "use strict";

        return BaseController.extend("app.controller.Home", {
            model: {
                userName: ""
            },

            onInit: function () {
                this.model.userName = cognito.getUsername();
                this.getView().setModel(new JSONModel(this.model));
            },

            helloPress: function () {
                api.get("/api/hi", {}, function (data) {
                    MessageToast.show("Hello user " + data.userInfo.username + " (" + data.userInfo.email + ")");
                });
            },

            onLogOffPress: function () {
                cognito.signOut();
                this.getOwnerComponent().getTargets().display("login");
            }
        });
    }
);

To handle ajax requests I’ve create an api model. This model injects jwt inside every request.

sap.ui.define([
    "sap/m/MessageToast",
    "app/model/cognito"
], function (MessageToast, cognito) {
    "use strict";

    var backend = "";

    return {
        get: function (uri, params, cb) {
            params = params || {};
            params._jwt = cognito.getJwt();
            sap.ui.core.BusyIndicator.show(1000);

            jQuery.ajax({
                type: "GET",
                contentType: "application/json",
                data: params,
                url: backend + uri,
                cache: false,
                dataType: "json",
                async: true,
                success: function (data, textStatus, jqXHR) {
                    sap.ui.core.BusyIndicator.hide();
                    cb(data);
                },
                error: function (data, textStatus, jqXHR) {
                    sap.ui.core.BusyIndicator.hide();
                    switch (data.status) {
                        case 403: // Forbidden
                            MessageToast.show('Auth error');
                            break;
                        default:
                            console.log('Error', data);
                    }
                }
            });
        }
    };
});

That’s the frontend. Now it’s time to backend. Our Backend will be a simple Lumen server.

use App\Http\Middleware;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Laravel\Lumen\Application;

(new Dotenv\Dotenv(__DIR__ . "/../env/"))->load();

$app = new Application();

$app->singleton(ExceptionHandler::class, App\Exceptions\Handler::class);

$app->routeMiddleware([
    'cognito' => Middleware\AuthCognitoMiddleware::class,
]);

$app->register(App\Providers\RedisServiceProvider::class);

$app->group([
    'middleware' => 'cognito',
    'namespace'  => 'App\Http\Controllers',
], function (Application $app) {
    $app->get("/api/hi", "DemoController@hi");
});

$app->run();

As you can see I’ve created a middelware to handle the authentication. This middleware will check the jwt provided by the frontend. We will use “spomky-labs/jose” library to validate the token.

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Jose\Factory\JWKFactory;
use Jose\Loader;
use Monolog\Logger;
use Symfony\Component\Cache\Adapter\RedisAdapter;

class AuthCognitoMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        try {
            $payload = $this->getPayload($request->get('_jwt'), $this->getJwtWebKeys());
            config([
                "userInfo" => [
                    'username' => $payload['cognito:username'],
                    'email'    => $payload['email'],
                ],
            ]);
        } catch (\Exception $e) {
            $log = app(Logger::class);
            $log->alert($e->getMessage());

            return response('Token Error', 403);
        }

        return $next($request);
    }

    private function getJwtWebKeys()
    {
        $url      = sprintf(
            'https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json',
            getenv('AWS_REGION'),
            getenv('AWS_COGNITO_POOL')
        );
        $cacheKey = sprintf('JWKFactory-Content-%s', hash('sha512', $url));

        $cache = app(RedisAdapter::class);

        $item = $cache->getItem($cacheKey);
        if (!$item->isHit()) {
            $item->set($this->getContent($url));
            $item->expiresAfter((int)getenv("TTL_JWK_CACHE"));
            $cache->save($item);
        }

        return JWKFactory::createFromJKU($url, false, $cache);
    }

    private function getPayload($accessToken, $jwtWebKeys)
    {
        $loader  = new Loader();
        $jwt     = $loader->loadAndVerifySignatureUsingKeySet($accessToken, $jwtWebKeys, ['RS256']);
        $payload = $jwt->getPayload();

        return $payload;
    }

    private function getContent($url)
    {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_URL            => $url,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
        ]);
        $content = curl_exec($ch);
        curl_close($ch);

        return $content;
    }
}

To validate jwt Cognito tokens we need to obtain JwtWebKeys from this url

https://cognito-idp.my_aws_region.amazonaws.com/my_aws_cognito_pool_id/.well-known/jwks.json

That means that we need to fetch this url within every backend request, and that’s not cool. spomky-labs/jose allows us to use a cache to avoid fetch the request again and again. This cache is an instance of something that implementes the interface Psr\Cache\CacheItemPoolInterface. I’m not going to create a Cache from scratch. I’m not crazy. I’ll use symfony/cache here with a Redis adapter

And basically that’s all. Full application in my github

Advertisements

Playing with IoT, MQTT, Arduino and Raspberry Pi. Building a dashboard with OpenUI5

I’ve been playing with MQTT in previous posts. Today I want to build a simple dashboard. Basically because I’ve got a 3.5inch display for my Raspberry Py and I want to use it. The idea is set up my Rasperry Pi as a web kiosk and display the MQTT variables in real time using websockets. Let’s start.

Set up Raspberry Pi as a web kiosk is pretty straightforward. You only need to follow instructions detailed here. Now we will prepare the MQTT inputs. Today we’re going to reuse one example of previous post. A potentiometer controlled by a nodemcu microcontroller connected to our MQTT server via Wifi.

We also will build another circuit using a Arduino board and a ethernet Shield.

With this circuit we’ll register the temperature (using a LM35 temperature sensor), a photo resistor (CDS) to show the light level and a relay to switch on/off a light bulb. The Idea of the circuit is emit the temperature and light level to mosquitto mqtt server and listen to switch status form mqtt server to fire the relay. That’s the arduino code

#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>

const int photocellPin = 1;
const int tempPin = 0;
const int relayPin = 9;
bool lightStatus = false;

const byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

// mqtt configuration
const char* mqttServer = "192.168.1.104";
const int mqttPort = 1883;
const String topicLightChange = "sensors/arduino/light/change";
const String topicLightStatus = "sensors/arduino/light/status";
const String topicTemp = "sensors/arduino/temperature/room1";
const String topicLight = "sensors/arduino/light/room1";
const char* clientName = "com.gonzalo123.arduino";

EthernetClient ethClient;
PubSubClient client(ethClient);

void mqttReConnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    if (client.connect(clientName)) {
      Serial.println("connected");
      client.subscribe(topicLightChange.c_str());
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void mqttEmit(String topic, String value) {
  if (client.publish((char*) topic.c_str(), (char*) value.c_str())) {
    //Serial.print("Publish ok (topic: ");
    //Serial.print(topic);
    //Serial.print(", value: ");
    //Serial.print(value);
    //Serial.println(")");
  } else {
    Serial.println("Publish failed");
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] payload: ");
  String data;
  for (int i = 0; i < length; i++) {
    data += (char)payload[i];
  }

  if (strcmp(topic, topicLightChange.c_str()) == 0) {
    lightStatus = (data == "1") ? true : false;
    Serial.print(data);
  }
  
  Serial.println("");
}

void setup()
{
  Serial.begin(9600);
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);
  
  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
  }

  delay(1500);
}

void loop()
{
  if (!client.connected()) {
    mqttReConnect();
  }

  client.loop();

  if (lightStatus == 1) {
    digitalWrite(relayPin, HIGH);
  } else {
    digitalWrite(relayPin, LOW);
  }
  mqttEmit(topicLightStatus, lightStatus ? "1" : "0");
  mqttEmit(topicLight, (String) analogRead(photocellPin));
  mqttEmit(topicTemp, (String) ((5.0 * analogRead(tempPin) * 100.0) / 1024.0));

  delay(500);
}

Now we’re going to work with dashboard. This days I’m working with OpenUI5 within various projects and because of that we’ll use this library to build the dashboard. we’ll build something like this:

Basically it’s a view

<mvc:View
        controllerName="gonzalo123.controller.Controller"
        height="100%"
        width="100%"
        xmlns="sap.m"
        xmlns:mvc="sap.ui.core.mvc"
        xmlns:app="http://schemas.sap.com/sapui5/extension/sap.ui.core.CustomData/1"
>
    <IconTabBar expandable="false"
                stretchContentHeight="true"
                class="sapUiResponsiveContentPadding">
        <items>
            <IconTabFilter icon="sap-icon://bbyd-dashboard">
                <TileContainer>
                    <StandardTile
                            icon="sap-icon://explorer"
                            number="{/potentiometer}"
                            numberUnit="%"
                            title="{i18n>potentiometer}"/>
                    <StandardTile
                            icon="sap-icon://temperature"
                            number="{/temperature}"
                            numberUnit="ºC"
                            title="{i18n>temperature}"/>
                    <StandardTile
                            icon="sap-icon://lightbulb"
                            number="{/light/level}"
                            title="{i18n>light}"/>
                </TileContainer>
            </IconTabFilter>
            <IconTabFilter icon="sap-icon://lightbulb">
                <Page showHeader="false"
                      enableScrolling="true">
                    <List>
                        <InputListItem label="{i18n>light}">
                            <Switch state="{/light/status}"
                                    change="onStatusChange"/>
                        </InputListItem>
                    </List>
                </Page>
            </IconTabFilter>
        </items>
    </IconTabBar>
</mvc:View>

And a controller:

sap.ui.define([
        'jquery.sap.global',
        'sap/ui/core/mvc/Controller',
        'sap/ui/model/json/JSONModel',
        "sap/ui/model/resource/ResourceModel",
        'gonzalo123/model/io'
    ],

    function (jQuery, Controller, JSONModel, ResourceModel, io) {
        "use strict";

        io.connect("//192.168.1.104:3000/");

        return Controller.extend("gonzalo123.controller.Controller", {
            model: new JSONModel({
                light: {
                    status: false,
                    level: undefined
                },
                potentiometer: undefined,
                temperature: undefined
            }),

            onInit: function () {
                var model = this.model;
                io.on('mqtt', function (data) {
                    switch (data.topic) {
                        case 'sensors/arduino/temperature/room1':
                            model.setProperty("/temperature", data.payload);
                            break;
                        case 'sensors/arduino/light/room1':
                            model.setProperty("/light/level", data.payload);
                            break;
                        case 'sensors/nodemcu/potentiometer/room1':
                            model.setProperty("/potentiometer", data.payload);
                            break;
                        case 'sensors/arduino/light/status':
                            model.setProperty("/light/status", data.payload == "1");
                            break;
                    }
                });

                this.getView().setModel(this.model);

                var i18nModel = new ResourceModel({
                    bundleName: "gonzalo123.i18n.i18n"
                });

                this.getView().setModel(i18nModel, "i18n");
            },

            onStatusChange: function () {
                io.emit('mqtt', {
                    topic: 'sensors/arduino/light/change',
                    payload: (this.getView().getModel().oData.light.status ? "1" : "0")
                });
            }
        });
    }
);

The real time part we need a gateway between websockets and mqtt data. We’ll use socket.io. Here is the server:

var mqtt = require('mqtt');
var mqttClient = mqtt.connect('mqtt://192.168.1.104');
var httpServer = require('http').createServer();
io = require('socket.io')(httpServer, {origins: '*:*'});

io.on('connection', function(client){
    client.on('mqtt', function(msg){
        console.log("ws", msg);
        mqttClient.publish(msg.topic, msg.payload.toString());
    })
});

mqttClient.on('connect', function () {
    mqttClient.subscribe('sensors/#');
});

mqttClient.on('message', function (topic, message) {
    console.log("mqtt", topic, message.toString());
    io.sockets.emit('mqtt', {
        topic: topic,
        payload: message.toString()
    });
});

httpServer.listen(3000, '0.0.0.0');

Hardware

  • 1 Arduino Uno
  • 1 NodeMCU (V3)
  • 1 potentiometer
  • 1 Servo (SG90)
  • 1 Raspberry Pi 3
  • 3.5inch Display Hat for Raspberry Pi
  • LM35
  • CDS
  • pull down resistor

Source code available in my github account

Encrypt Websocket (socket.io) communications

I’m a big fan of WebSockets and socket.io. I’ve written a lot of about it. In last posts I’ve written about socket.io and authentication. Today we’re going to speak about communications.

Imagine we’ve got a websocket server and we connect our application to this server (even using https/wss). If we open our browser’s console we can inspect our WebSocket communications. We also can enable debugging. This works in a similar way than when we start the promiscuous mode within our network interface. We will see every packets. Not only the packets that server is sending to us.

If we send send sensitive information over websockets, that means than one logged user can see another ones information. We can separate namespaces in our socket.io server. We also can do another thing: Encrypt communications using crypto-js.

I’ve created one small wrapper to use it with socket.io.
We can install our server dependency

npm g-crypt

And install our client dependency with bower

bower install g-crypt

And use it in our server

var io = require('socket.io')(3000),
    Crypt = require("g-crypt"),
    passphrase = 'super-secret-passphrase',
    crypter = Crypt(passphrase);

io.on('connection', function (socket) {
    socket.on('counter', function (data) {
        var decriptedData = crypter.decrypt(data);
        setTimeout(function () {
            console.log("counter status: " + decriptedData.id);
            decriptedData.id++;
            socket.emit('counter', crypter.encrypt(decriptedData));
        }, 1000);
    });
});

And now a simple HTTP application

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
Open console to see the messages

<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script src="assets/cryptojslib/rollups/aes.js"></script>
<script src="assets/g-crypt/src/Crypt.js"></script>
<script>
    var socket = io('http://localhost:3000/'),
        passphrase = 'super-secret-passphrase',
        crypter = Crypt(passphrase),
        id = 0;

    socket.on('connect', function () {
        console.log("connected! Let's start the counter with: " + id);
        socket.emit('counter', crypter.encrypt({id: id}));
    });

    socket.on('counter', function (data) {
        var decriptedData = crypter.decrypt(data);
        console.log("counter status: " + decriptedData.id);
        socket.emit('counter', crypter.encrypt({id: decriptedData.id}));
    });
</script>

</body>
</html>

Now our communications are encrypted and logged user cannot read another ones data.

Library is a simple wrapper

Crypt = function (passphrase) {
    "use strict";
    var pass = passphrase;
    var CryptoJSAesJson = {
        parse: function (jsonStr) {
            var j = JSON.parse(jsonStr);
            var cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Base64.parse(j.ct)});
            if (j.iv) cipherParams.iv = CryptoJS.enc.Hex.parse(j.iv);
            if (j.s) cipherParams.salt = CryptoJS.enc.Hex.parse(j.s);
            return cipherParams;
        },
        stringify: function (cipherParams) {
            var j = {ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64)};
            if (cipherParams.iv) j.iv = cipherParams.iv.toString();
            if (cipherParams.salt) j.s = cipherParams.salt.toString();
            return JSON.stringify(j);
        }
    };

    return {
        decrypt: function (data) {
            return JSON.parse(CryptoJS.AES.decrypt(data, pass, {format: CryptoJSAesJson}).toString(CryptoJS.enc.Utf8));
        },
        encrypt: function (data) {
            return CryptoJS.AES.encrypt(JSON.stringify(data), pass, {format: CryptoJSAesJson}).toString();
        }
    };
};

if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
    CryptoJS = require("crypto-js");
    module.exports = Crypt;
} else {
    window.Crypt = Crypt;
}

Library available in my github and also we can use it using npm and bower.

Sharing authentication between socket.io and a PHP frontend

Normally, when I work with websockets, my stack is a socket.io server and a Silex frontend. Protect a PHP frontend with one kind of authentication of another is pretty straightforward. But if we want to use websockets, we need to set up another server and if we protect our frontend we need to protect our websocket server too.

If our frontend is node too (express for example), sharing authentication is more easy but at this time we we want to use two different servers (a node server and a PHP server). I’ve written about it too but today we`ll see another solution. Let’s start.

Imagine we have this simple Silex application. It has three routes:

  • “/” a public route
  • “/login” to perform the login action
  • “/private” a private route. If we try to get here without a valid session we’ll get a 403 error

And this is the code. It’s basically one example using sessions taken from Silex documentation:

use Silex\Application;
use Silex\Provider\SessionServiceProvider;
use Silex\Provider\TwigServiceProvider;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

$app = new Application();

$app->register(new SessionServiceProvider());
$app->register(new TwigServiceProvider(), [
    'twig.path' => __DIR__ . '/../views',
]);

$app->get('/', function (Application $app) {
    return $app['twig']->render('home.twig');
});

$app->get('/login', function () use ($app) {
    $username = $app['request']->server->get('PHP_AUTH_USER', false);
    $password = $app['request']->server->get('PHP_AUTH_PW');

    if ('gonzalo' === $username && 'password' === $password) {
        $app['session']->set('user', ['username' => $username]);

        return $app->redirect('/private');
    }

    $response = new Response();
    $response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', 'site_login'));
    $response->setStatusCode(401, 'Please sign in.');

    return $response;
});

$app->get('/private', function () use ($app) {
    $user = $app['session']->get('user');
    if (null === $user) {
        throw new AccessDeniedHttpException('Access Denied');
    }

    return $app['twig']->render('private.twig', [
        'username'  => $user['username']
    ]);
});

$app->run();

Our “/private” route also creates a connection with our websocket server.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
Welcome {{ username }}!

<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script>
    var socket = io('http://localhost:3000/');
    socket.on('connect', function () {
        console.log("connected!");
    });
    socket.on('disconnect', function () {
        console.log("disconnected!");
    });
</script>

</body>
</html>

And that’s our socket.io server. A really simple one.

var io = require('socket.io')(3000);

It works. Our frontend is protected. We need to login with our credentials (in this example “gonzalo/password”), but everyone can connect to our socket.io server. The idea is to use our PHP session to protect our socket.io server too. In fact is very easy how to do it. First we need to pass our PHPSESSID to our socket.io server. To do it, when we perform our socket.io connection in the frontend, we pass our session id

<script>
    var socket = io('http://localhost:3000/', {
        query: 'token={{ sessionId }}'
    });
    socket.on('connect', function () {
        console.log("connected!");
    });
    socket.on('disconnect', function () {
        console.log("disconnect!");
    });
</script>

As well as we’re using a twig template we need to pass sessionId variable

$app->get('/private', function () use ($app) {
    $user = $app['session']->get('user');
    if (null === $user) {
        throw new AccessDeniedHttpException('Access Denied');
    }

    return $app['twig']->render('private.twig', [
        'username'  => $user['username'],
        'sessionId' => $app['session']->getId()
    ]);
});

Now we only need to validate the token before stabilising connection. Socket.io provides us a middleware to perform those kind of operations. In this example we’re using PHP sessions out of the box. How can we validate it? The answer is easy. We only need to create a http client (in the socket.io server) and perform a request to a protected route (we’ll use “/private”). If we’re using a different provider to store our sessions (I hope you aren’t using Memcached to store PHP session, indeed) you’ll need to validate our sessionId against your provider.

var io = require('socket.io')(3000),
    http = require('http');

io.use(function (socket, next) {
    var options = {
        host: 'localhost',
        port: 8080,
        path: '/private',
        headers: {Cookie: 'PHPSESSID=' + socket.handshake.query.token}
    };

    http.request(options, function (response) {
        response.on('error', function () {
            next(new Error("not authorized"));
        }).on('data', function () {
            next();
        });
    }).end();
});

io.on('connection', function () {
    console.log("connected!");
});

Ok. This example works but we’re generating dynamically a js file injecting our PHPSESSID. If we want to extract the sessionId from the request we can use document.cookie but sometimes it doesn’t work. That’s because HttpOnly. HttpOnly is our friend if we want to protect our cookies against XSS attacks but in this case our protection difficults our task.

We can solve this problem performing a simple request to our server. We’ll create a new route (a private route) called ‘getSessionID’ that gives us our sessionId.

$app->get('/getSessionID', function (Application $app) {
    $user = $app['session']->get('user');
    if (null === $user) {
        throw new AccessDeniedHttpException('Access Denied');
    }

    return $app->json($app['session']->getId());
});

So before establishing the websocket we just need to create a GET request to our new route to obtain the sessionID.

var io = require('socket.io')(3000),
    http = require('http');

io.use(function (socket, next) {
    var sessionId = socket.handshake.query.token,
        options = {
            host: 'localhost',
            port: 8080,
            path: '/getSessionID',
            headers: {Cookie: 'PHPSESSID=' + sessionId}
        };

    http.request(options, function (response) {
        response.on('error', function () {
            next(new Error("not authorized"));
        });
        response.on('data', function (chunk) {
            var sessionIdFromRequest;
            try {
                sessionIdFromRequest = JSON.parse(chunk.toString());
            } catch (e) {
                next(new Error("not authorized"));
            }

            if (sessionId == sessionIdFromRequest) {
                next();
            } else {
                next(new Error("not authorized"));
            }
        });
    }).end();
});

io.on('connection', function (socket) {
    setInterval(function() {
        socket.emit('hello', {hello: 'world'});
    }, 1000);
});

And thats all. You can see the full example in my github account.

Working with Ionic and PHP Backends. Remote debugging with PHP7 and Xdebug working with real devices

Sometimes I speak with PHP developers and they don’t use remote debugging in their development environments. Some people don’t like to use remote debugging. They prefer to use TDD and rely on the unit tests. That’s a good point of view, but sometimes they don’t use remote debugging only because they don’t know how to do it, and that’s inadmissible. Remote debugger is a powerful tool especially to handle with legacy applications. I’ve using xdebug for years with my linux workstation for years. This days I’m using Mac and it’s also very simple to set up xdebug here.

First we need to install PHP:

brew install php70

Then Xdebug

brew install php70-xdebug

(in a Ubuntu box we only need to use apt-get instead of brew)

Now we need to setup xdebug to enable remote debugging:
In a standard installation xdebug configuration is located at: /usr/local/etc/php/7.0/conf.d/ext-xdebug.ini

[xdebug]
zend_extension="/usr/local/opt/php70-xdebug/xdebug.so"

xdebug.remote_enable=1
xdebug.remote_port=9000
xdebug.profiler_enable=0
xdebug.profiler_output_dir="/tmp"
xdebug.idekey= "PHPSTORM"
xdebug.remote_connect_back = 1
xdebug.max_nesting_level = 250

And basically that’s all. To set/unset the cookie you can use one bookmarklet in your browser (you can generate your bookmarklets here). Or use a Chrome extension to enable xdebug.

Now se only need to start the built-in server with

php -S 0.0.0.0:8080

And remote debugging will be available
Remote debugger works this way:

  • We open on port within our IDE. In my case PHPStorm (it happens when we click on “Start listening for PHP debug connections”)
  • We set one cookie in our browser (it happens when click on Chrome extension)
  • When our server receives one request with the cookie, it connects to the port that our IDE opens (usually port 9000). If you use a personal firewall in your workstation, ensure that you allow incoming connections to this port.

Nowadays I’m involved with several projects building hybrid applications with Apache Cordova. In the Frontend I’m using ionic and Silex in the Backend. When I’m working with hybrid applications normally I go through two phases.

In the first one I build a working prototype. To to this I run a local server and I use my browser to develop the application. This phase is very similar than a traditional Web development process. If we also set up properly LiveReload, our application will be reloaded each time we change one javaScript file. Ionic framework integrates LiveReload and we only need to run:

ionic serve -l

to start our application. We also need to start our backend server. For example

php -S 0.0.0.0:8080 -t api/www

Now we can debug our Backend with remote debugger and Frontend with Chrome’s developer’s tools. Chrome also allows us to edit Frontend files and save them within the filesystem using workspaces. This phase is the easy one. But sooner or later we’ll need start working with a real device. We need a real device basically if we use plugins such as Camera plugin, Geolocation plugin, or things like that. OK there are emulators, but usually emulators don’t allow to use all plugins in the same way than we use then with a real device. Chrome also allow us to see the console logs of the device from our workstation. OK we can see all logs of our plugged Android device using “adb logcat” but follow the flow of our logs with logcat is similar than understand Matrix code. It’s a mess.

If we plug our android device to our computer and we open with Chrome:

chrome://inspect/#devices

We can see our device’s console, use breakpoints and things like that. Cool, isn’t it? Of course it only works if we compile our application without “–release” option. We can do something similar with Safary and iOS devices.

With ionic if we want to use LiveReload from the real device and not to recompile and re-install again and again our application each time we change our javaScript files, we can run the application using

ionic run android --device -l

When we’re developing our application and we’re in this phase we also need to handle with CORS. CORS isn’t a problem when we run our hybrid application in production. When we run the hybrid application with our device our “origin” is the local filesystem. That’s means CORS don’t apply, but when we run our application in the device, but served from our computer (when we use “-l” option), our origin isn’t local filesystem. So if our Backend is served from another origin we need to enable CORS.

We can enable CORS in the backend. I’ve written about it here, but ionic people allows us a easier way. We can set up a local proxy to serve our backend through the same origin than the application does and forget about CORS. Here we can read a good article about it.

Anyway if we want to start the remote debugger we need to create one cookie called XDEBUG_SESSION. In the browser we can use chrome extension, but when we inspect the plugged device isn’t so simple. It would be cool that ionic people allows us to inject cookies to our proxy server. I’ve try to see how to do it with ionic-cli. Maybe is possible but I didn’t realize how to do it. Because of that I’ve created a simple AngularJS service to inject this cookie. Then, if I start listening debug connections in my IDE I’ll be able to use remote debugger as well as I do when I work with the browser.

First we need to install service via Bower:

bower install ng-xdebugger --save

Now we need to include javaScript files

<script src="lib/angular-cookies/angular-cookies.min.js"></script>
<script src="lib/ng-xdebugger/dist/gonzalo123.xdebugger.min.js"></script>

then we add our service to the project.

angular.module("starter", ["ionic", "gonzalo123.xdebugger"])

Now we only need to configure our application and set de debugger key (it must be the same key than we use within the server-side configuration of xdebug)

.config(function (xdebuggerProvider) {
        xdebuggerProvider.setKey('PHPSTORM');
    })
})

And that’s all. The service is very simple. It only uses one http interceptor to inject the cookie in our http requests:

(function () {
    "use strict";

    angular.module("gonzalo123.xdebugger", ["ngCookies"])
        .provider("xdebugger", ['$httpProvider', function ($httpProvider) {
            var debugKey;

            this.$get = function () {
                return {
                    getDebugKey: function () {
                        return debugKey;
                    }
                };
            };

            this.setKey = function (string) {
                if (string) {
                    debugKey = string;
                    $httpProvider.interceptors.push("xdebuggerCookieInterceptor");
                }
            };
        }])

        .factory("xdebuggerCookieInterceptor", ['$cookieStore', 'xdebugger', function ($cookieStore, xdebugger) {
            return {
                response: function (response) {
                    $cookieStore.put("XDEBUG_SESSION", xdebugger.getDebugKey());

                    return response;
                }
            };
        }])
    ;
})();

And of course you can see the whole project in my github account.

Book review: Socket.IO Cookbook

Last summer I collaborated as a technical reviewer in the book “Socket.IO Cookbook” written by Tyson Cadenhead and finally I’ve got the book in my hands

I’m a big fan of real time technologies and I’m normally Socket.io user. Because of that, when people of Packt Publishing contacted me to join to the project as technical reviewer my answer was yes. I’ve got serious problems nowadays to find time to pet projects and extra activities, but if there’re WebSockets inside I cannot resists.

The book is correct and it’s a good starting point to event-based communication with JavaScript. I normally don’t like beginners books (even if I’m a beginner in the technology). I don’t like the books where author explains how to do one thing that I can see how to do it within the website of the. OK. This book isn’t one of those of books. The writer don’t assume reader is a totally newbie. Because of that newbies sometimes can be lost in some chapters, but this exactly the way we all learn new technologies. I like the way Tyson introduces concepts about socket.io.

The book is focused in JavaScript and also uses JavaScript to the backend (with node). Maybe I miss the integration with non-JavaScript environments, but as socket.io is a javascript library I understand that the usage of JavaScript in all application lifecycle is a good approach.

IMG_20151106_204902_jpg

Also those days I was reading and playing a little bit with WebRTC and the book has one chapter about it! #cool

i18n AngularJS provider

There’s more than one way to perform i18n translations within our AngularJS projects. IMHO the best one is https://angular-translate.github.io/, but today I’m going to show you how I’m doing translations in my small AngularJS projects (normally Ionic projects).

I’ve packaged my custom solution and I also create one bower package ready to use via bower command line:

bower install ng-i8n --save

First we add our provider

<script src='lib/ng-i8n/dist/i8n.min.js'></script>

And now we add our new module (‘gonzalo123.i18n’) to our AngularJS project

angular.module('G', ['ionic', 'ngCordova', 'gonzalo123.i18n'])

Now we’re ready to initialise our provider with the default language and translation data

    .config(function (i18nProvider, Conf) {
        i18nProvider.init(Conf.defaultLang, Conf.lang);
    })

I like to use constants to store default lang and translation table, but it isn’t necessary. We can just pass the default language and Lang object to our provider

    .constant('Conf', {
        defaultLang: 'es',
        lang: {
            HI: {
                en: 'Hello',
                es: 'Hola'
            }
        }
    })

And that’s all. We can translate key in templates (the project also provides a filter):

<h1 class="title">{{ 'HI' | i18n }}</h1>

And also inside our controllers

    .controller('HomeController', function ($scope, i18n) {
        $scope.hi = i18n.traslate('HI');
    })

If we need to change user language, we only need to trigger ‘use’ function:

    .controller('HomeController', function ($scope, i18n) {
        $scope.changeLang = function(lang) {
            i18n.use(lang);
        };
    })

Here we can see the code of our provider:

(function () {
    "use strict";

    angular.module('gonzalo123.i8n', [])
        .provider('i18n', function () {
            var myLang = {},
                userLang = 'en',
                translate;

            translate = function (key) {
                if (myLang.hasOwnProperty(key)) {
                    return myLang[key][userLang] || key;
                } else {
                    return key;
                }
            };

            this.$get = function () {
                return {
                    use: this.use,
                    translate: translate
                };
            };

            this.use = function (lang) {
                userLang = lang;
            };

            this.init = function (lang, conf) {
                userLang = lang;
                myLang = conf;
            };
        })

        .filter('i18n', ['i18n', function (i18n) {
            var i18nFilter = function (key) {
                return i18n.translate(key);
            };

            i8nFilter.$stateful = true;

            return i18nFilter;
        }])
    ;
})();

Anyway the project is in my github account

PHP Dumper using Websockets

Another crazy idea. I want to dump my backend output in the browser’s console. There’re several PHP dumpers. For example Raul Fraile’s LadyBug. There’re also libraries to do exactly what I want to do, such as Chrome Logger. But I wanted to use Websockets and dump values in real time, without waiting to the end of backend script. Why? The answer is simple: Because I wanted to it 🙂

I’ve written several post about Websockets, Silex, PHP. In this case I’ll use a similar approach than the previous posts. First I’ve created a simple Webscocket server with socket.io. This server also starts a Express server to handle internal messages from the Silex Backend

var CONF = {
        IO: {HOST: '0.0.0.0', PORT: 8888},
        EXPRESS: {HOST: '0.0.0.0', PORT: 26300}
    },
    express = require('express'),
    expressApp = express(),
    server = require('http').Server(expressApp),
    io = require('socket.io')(server, {origins: 'localhost:*'})
    ;

expressApp.get('/:type/:session/:message', function (req, res) {
    console.log(req.params);
    var session = req.params.session,
        type = req.params.type,
        message = req.params.message;

    io.sockets.emit('dumper.' + session, {title: type, data: JSON.parse(message)});
    res.json('OK');
});

io.sockets.on('connection', function (socket) {
    console.log("Socket connected!");
});

expressApp.listen(CONF.EXPRESS.PORT, CONF.EXPRESS.HOST, function () {
    console.log('Express started');
});

server.listen(CONF.IO.PORT, CONF.IO.HOST, function () {
    console.log('IO started');
});

Now we create a simple Service provider to connect our Silex Backend to our Express server (and send the dumper’s messages using the websocket connection)

<?php

namespace Dumper\Silex\Provider;

use Silex\Application;
use Silex\ServiceProviderInterface;
use Dumper\Dumper;
use Silex\Provider\SessionServiceProvider;
use GuzzleHttp\Client;

class DumperServiceProvider implements ServiceProviderInterface
{
    private $wsConnector;
    private $client;

    public function __construct(Client $client, $wsConnector)
    {
        $this->client = $client;
        $this->wsConnector = $wsConnector;
    }

    public function register(Application $app)
    {
        $app->register(new SessionServiceProvider());

        $app['dumper'] = function () use ($app) {
            return new Dumper($this->client, $this->wsConnector, $app['session']->get('uid'));
        };

        $app['dumper.init'] = $app->protect(function ($uid) use ($app) {
            $app['session']->set('uid', $uid);
        });

        $app['dumper.uid'] = function () use ($app) {
            return $app['session']->get('uid');
        };
    }

    public function boot(Application $app)
    {
    }
}

Finally our Silex Application looks like that:

include __DIR__ . '/../vendor/autoload.php';

use Silex\Application;
use Silex\Provider\TwigServiceProvider;
use Dumper\Silex\Provider\DumperServiceProvider;
use GuzzleHttp\Client;

$app = new Application([
    'debug' => true
]);

$app->register(new DumperServiceProvider(new Client(), 'http://192.168.1.104:26300'));

$app->register(new TwigServiceProvider(), [
    'twig.path' => __DIR__ . '/../views',
]);

$app->get("/", function (Application $app) {
    $uid = uniqid();

    $app['dumper.init']($uid);

    return $app['twig']->render('index.twig', [
        'uid' => $uid
    ]);
});

$app->get('/api/hello', function (Application $app) {
    $app['dumper']->error("Hello world1");
    $app['dumper']->info([1,2,3]);

    return $app->json('OK');
});


$app->run();

In the client side we have one index.html. I’ve created Twig template to pass uid to the dumper object (the websocket channel to listen to), but we also can fetch this uid from the backend with one ajax call.

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Dumper example</title>
</head>
<body>

<a href="#" onclick="api('hello')">hello</a>

<!-- We use jQuery just for the demo. Library doesn't need jQuery -->
<script src="assets/jquery/dist/jquery.min.js"></script>
<!-- We load the library -->
<script src="js/dumper.js"></script>

<script>
    dumper.startSocketIo('{{ uid }}', '//localhost:8888');
    function api(name) {
        // we perform a remote api ajax call that triggers websockets
        $.getJSON('/api/' + name, function (data) {
            // Doing nothing. We only call the api to test php dumper
        });
    }
</script>
</body>
</html>

I use jQuery to handle ajax request and to connect to the websocket dumper object (it doesn’t deppend on jQuery, only depend on socket.io)

var dumper = (function () {
    var socket, sessionUid, socketUri, init;

    init = function () {
        if (typeof(io) === 'undefined') {
            setTimeout(init, 100);
        } else {
            socket = io(socketUri);

            socket.on('dumper.' + sessionUid, function (data) {
                console.group('Dumper:', data.title);
                switch (data.title) {
                    case 'emergency':
                    case 'alert':
                    case 'critical':
                    case 'error':
                        console.error(data.data);
                        break;
                    case 'warning':
                        console.warn(data.data);
                        break;
                    case 'notice':
                    case 'info':
                    //case 'debug':
                        console.info(data.data);
                        break;
                    default:
                        console.log(data.data);
                }
                console.groupEnd();
            });
        }
    };

    return {
        startSocketIo: function (uid, uri) {
            var script = document.createElement('script');
            var node = document.getElementsByTagName('script')[0];

            sessionUid = uid;
            socketUri = uri;
            script.src = socketUri + '/socket.io/socket.io.js';
            node.parentNode.insertBefore(script, node);

            init();
        }
    };
})();

Source code is available in my github account

Handling private states within AngularJS applications

One typical task when we work with AngularJs application is login, and private states. We can create different states in our application. Something like this:

    .config(function ($stateProvider, $urlRouterProvider) {
        $stateProvider
            .state('state1', {
                url: '/state1',
                templateUrl: templates/state1.html,
                controller: 'State1Controller'
            })
            .state('state2', {
                url: '/state2',
                templateUrl: templates/state2.html,
                controller: 'State2Controller'
            })
        $urlRouterProvider.otherwise('/state1');
    })

One way to create private states is using $stateChangeStart event. We can mark our private states with state parameters:

    .state('privateState1', {
            url: '/privateState1',
            templateUrl: templates/privateState1.html,
            controller: 'PrivateState1Controller',
            data: {
                isPublic: false
            }
        })

And then we can check out this parameters within $stateChangeStart event, doing one thing or another depending on token is present or not

    .run(function ($rootScope) {
        $rootScope.$on("$stateChangeStart", function (event, toState) {
            if (toState.data && toState.data.isPublic) {
                // do something here with localstorage and auth token
            }
        });
    })

This method works, but last days, reading one project of Aaron K Saunders at github, I just realised that there’s another method. We can listen to $stateChangeError. Let me show you how can we do it.

The idea is to use resolve in our private states. With resolve we can inject objects to our state’s controllers, for example user information. This method is triggered before call to the controller, so that’s a good place to check if token is present. If it isn’t, then we can raise an error. This error will trigger $stateChangeError event, and here we can redirect the user to login state.

It sounds good, but we need to write resolve parameter in every private states, and that’s bored. Especially when all states are private except login state. To by-pass this problem we can use abstract states. The idea is simple, we define one abstract state with “resolve” and then we create our private states under this abstract state.

Here we can see one example: login state isn’t private, but state1 and state2 are private, indeed.

    .config(function ($stateProvider, $urlRouterProvider) {
        .state('login', {
            url: '/login',
            templateUrl: 'templates/login.html',
            controller: 'LoginController'
        })
        .state('private', {
            url: "/private",
            abstract: true,
            template: '<ui-view/>',
            resolve: {
                user: function (UserService) {
                    return UserService.init();
                }
            }
        })
        .state('private.state1', {
            url: '/state1',
            templateUrl: 'templates/state1.html',
            controller: 'State1Controller'
        })
    
        .state('private.state2', {
            url: '/privateState2',
            templateUrl: 'templates/state2.html',
            controller: 'State2Controller'
        });
    
        $urlRouterProvider.otherwise('/private/privateState1');
    })

Our UserService is a AngularJS service. This service provides three methods: init (the method that raises an error if token isn’t present), login (to perform login and validate credentials), and logout (to remove token from localstorage and redirects to login state)

    .service('UserService', function ($q, $state) {
        var user = undefined;

        var UserService = {
            init: function () {
                var deferred = $q.defer();

                // do something here to get user from localstorage

                setTimeout(function () {
                    if (user) {
                        deferred.resolve(user);
                    } else {
                        deferred.reject({error: "noUser"});
                    }
                }, 100);

                return deferred.promise;
            },

            login: function (userName, password) {
                // validate user and password here
            },

            logout: function () {
                // remove token from localstorage
                user = undefined;
                $state.go('login', {});
            }
        };

        return UserService
    })

And finally the magic in $stateChangeError

    .run(function ($rootScope, $state) {
        $rootScope.$on('$stateChangeError',
            function (event, toState, toParams, fromState, fromParams, error) {
                if (error && error.error === "noUser") {
                    $state.go('login', {});
                }
            });
    })

And that’s all. IMHO this solution is cleaner than $stateChangeStart method. What do you think?

WARNING!
Before publishing this post I realize that this technique doesn’t work 100% correctly. Maybe is my implementation but I tried to use it with an ionic application and it doesn’t work with android. Something kinda weird. It works with web applications, it works with IOS, but it doesn’t work with Android. It looks like a bug (not sure about it). Blank screen instead of showing the template (but controller is loaded). We can see this anomalous situation using “ionic serve -l” (IOS ok and Android Not Ok)

To bypass this problem I tried a workaround. instead of using abstract states I create normal states, but to avoid to write again and again the resolve function to mark private states, I create a privateState provider

    .provider('privateState', function () {
        this.$get = function () {
            return {};
        };
    
        this.get = function(obj) {
            return angular.extend({
                resolve: {
                    user: function (UserService) {
                        return UserService.init();
                    }
                }
            }, obj);
        }
    })

Now I can easily create private states without writing ‘resolve’ function.

    .config(function ($stateProvider, $urlRouterProvider, privateStateProvider) {
        $urlRouterProvider.otherwise('/home');

        $stateProvider
            .state('home', privateStateProvider.get({
                url: '/home',
                templateUrl: 'templates/home.html',
                controller: 'HomeController'
            }))
        ;
    })

Building a AngularJS provider for hello.js library

This days I’ve been playing with hello.js. Hello is a A client-side Javascript SDK for authenticating with OAuth2 web services. It’s pretty straightforward to use and well explained at documentation. I want to use it within AngularJS projects. OK, I can include the library and use the global variable “hello”, but it isn’t cool. I want to create a reusable module and available with Bower. Let’s start.

Imagine one simple AngularJS application

(function () {
    angular.module('G', [])
        .config(function ($stateProvider, $urlRouterProvider) {
            $urlRouterProvider.otherwise("/");
            $stateProvider
                .state('login', {
                    url: "/",
                    templateUrl: "partials/home.html",
                    controller: "LoginController"
                })
                .state('home', {
                    url: "/login",
                    template: "partials/home.html"
                });
        })

        .controller('LoginController', function ($scope) {
            $scope.login = function () {
            };
        })
})();

Now we can include our references within our bower.json file

"dependencies": {
    "hello": "~1.4.1",
    "ng-hello": "*"
  }

and append those references to our index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title>G</title>

    <script type="text/javascript" src="assets/hello/dist/hello.all.js"></script>
    <script type="text/javascript" src="assets/ng-hello/dist/ng-hello.js"></script>
    <script src="js/app.js"></script>
</head>
<body ng-app="G">
<div ui-view></div>

</body>
</html>

Our ng-hello is just a service provider that wraps hello.js

(function (hello) {
    angular.module('ngHello', [])
        .provider('hello', function () {
            this.$get = function () {
                return hello;
            };

            this.init = function (services, options) {
                hello.init(services, options);
            };
        });
})(hello);

That’s means that we configure the service in config callback and in our run callback we can set up events

(function () {
    angular.module('G', ['ngHello'])
        .config(function ($stateProvider, $urlRouterProvider, helloProvider) {
            helloProvider.init({
                twitter: 'myTwitterToken'
            });

            $urlRouterProvider.otherwise("/");
            $stateProvider
                .state('login', {
                    url: "/",
                    templateUrl: "partials/home.html",
                    controller: "LoginController"
                })
                .state('home', {
                    url: "/login",
                    template: "partials/home.html"
                });
        })

        .run(function ($ionicPlatform, $log, hello) {
            hello.on("auth.login", function (r) {
                $log.log(r.authResponse);
            });
        });
})();

And finally we can perform a twitter login within our controller

(function () {
    angular.module('G')
        .controller('LoginController', function ($scope, hello) {
            $scope.login = function () {
                hello('twitter').login();
            };
        })
    ;
})();

And that’s all. You can see the whole library in my github account here