Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

User Authentication with Go and AngularJS

I want to set up an authentication system with tokens on my Go application, which use an AngularJS front-end.

What I'm looking for is advices about all steps to create a safe system. Here's my steps :

We will considerate a user entry is already written in our database.

1 - The user wants to sign in

The user goes to the index of the web app which prompts him to sign in.

With my app.yaml file, I declare my HTML file with dependencies for the AngularJS front-end:

application: authtest
version: 1
runtime: go
api_version: go1

handlers:
- url: /
  static_files: frontend/index.html
  upload: frontend/(.*\.html)

- url: /js
  static_dir: frontend/js

- url: /.*
  script: _go_app

Visual :

Alt text

Code:

<form ng-submit="signIn()" ng-controller="signInCtrl">
    <input type="email" ng-model="credentials.email" placeholder="Email" name="email">
    <input type="password" ng-model="credentials.password" placeholder="Password" name="password">
    <input type="submit" value="Sign in">
</form>

The user enters his credentials, and click on Sign in button.

2 - Request the server

The AngularJS module code:

<script>
    angular.module('authtest', ['ngCookies'])
    .controller('signInCtrl', ['$scope', '$http', '$window', '$cookieStore', '$route', function($scope, $http, $window, $cookieStore, $route){
        var credentials = {}
        $scope.signIn = function() {
            $http.post('/signin', credentials)
            .success(function (data, status) {
                if(status == 200) {
                    if(data.Access_token){
                        //set cookies with tokens
                        $cookieStore.put('access_token', data.Access_token);
                        $cookieStore.put('refresh_token', data.Refresh_token);
                        $route.reload();
                    }
                    else {
                        $window.alert('Wrong credentials, try again.');
                    }
                }
            })
            .error(function (data, status) {
                $window.alert('error: ' + data + '(errorStatus: ' + status + ')');
            });
        };
    }]);
</script>

The Angular module posts the form to /signin path, so the Go application needs to handle it:

package main
import (
    "net/http"
    "encoding/json"
    "fmt"
)
type SignInCredentials struct {
    Email string
    Password string
}
type Token struct {
    Access_token string
    Refresh_token string
    Id_token string
}
func init() {        //I use init() instead of main() because I'm using App Engine.
    http.HandleFunc("/signin", SignInHandler)
}
func SignInHandler (w http.ResponseWriter, r *http.Request) {
    decoder := json.NewDecoder(r.Body)
    var c SignInCredentials
    err := decoder.Decode(&c)
    if err != nil {
        panic()
    }
    var hashPassword = hash(c.Password) //Is it at a good level to hash it ?
    if isInDatabase(c.Email, hashPassword) == false {
        fmt.Fprintf(w, "") // Really useful ?
        return
    }

    var tokens Token
    /**
    * The user seems to be well stored in the database, but how
    * to manage my tokens ?
    */
    response, _ := json.Marshal(tokens)
    fmt.Fprintf(w, string(response))
}

Obviously, hashPassword(p string) hashes the user password and isInDatabase(e string, p string) is a boolean which checks if the provided credentials are stored in our database.

  • As I said inside the code, I'm wondering if it's the good moment to hash the password of the user, or maybe hash it in client-side ?
  • Print a zero-car string if the user is not in database is really useful or could I just return the function ?
  • The main question here is how to manage my tokens ? I often see tokens are mainly composed of access_token, refresh_token and id_token. I also know access_token is limited in time to use (the current session, ot 3600seconds for example) and we use refresh_token to have a fresh new access_token. But how to generate it and store it ?

3 - Handle response with AngularJS

As already written in AngularJS module code, when the user is well authenticated, we set cookies with provided tokens. Is it a good way ? I'm also wondering how to prevent XSRF (CSRF) attacks in my system ?

Thank you for your future answers and advices.

like image 278
jbltx Avatar asked Sep 22 '14 21:09

jbltx


1 Answers

Ok I've been using this approach (by Frederik Nakstad) and it seems to be ok so far.

The authentication is pretty straight forward. The user submits credentials over encrypted connection through a form like this:

<form name="loginform" class="uk-form" ng-submit="login()">
        <fieldset data-uk-margin>
            <legend><h2>Login</h2></legend>
            <div class="uk-form-row">
                <input class="uk-form-large" type="text" ng-model="cred.user" placeholder="Username" required>
            </div>
            <div class="uk-form-row">
                <input class="uk-form-large" type="password" ng-model="cred.password" placeholder="Password" required>
            </div>
            <div class="uk-form-row">
                <button ng-disabled="loginform.$invalid" class="uk-button uk-button-large" type="submit">Login</button>
            </div>
        </fieldset>
    </form>

Then the controller handles the response in the following way:

App.controller('LoginCtrl', function ($rootScope, $scope, $location, $window, authService) {
    $scope.cred = {}
    $scope.login = function () {
        if ($scope.loginform.$valid) {
            authService.auth(this.cred, function (stat) {
                if (stat === 200) {
                    $rootScope.loginStatus = authService.isLoggedIn();
                    $location.path('/test');
                } else {
                   ....
                   $location.path('/login');
                }
            });
            $scope.cred = {}
            $scope.loginform.$setPristine();
        }
    };
    $window.document.title = 'Admin Login';
});

The controller expects a 200 http status code (which the back-end should not respond with unless the authentication is successful), if the server responds with anything other than 200, the route is changed to login page again.

The tricky part is maintaining checks of the authentication status.

The way I implemented my authentication system is by adding custom attributes (like the guide but without a role system) to my route object in the following way:

App.js

...

$routeProvider.when('/login', {
            templateUrl: '/content/...',
            controller: 'Ctrl1',
            requireLogin: false
        }).when('/logout', {
            resolve: {
                logout: function ($location, $rootScope, authService) {
                    authService.logout(function (s) {
                        if (s === 200) {
                            $rootScope.loginStatus = authService.isLoggedIn();
                            $location.path('/test');
                        } else {
                            ....
                        }

                    });
                }
            },
            requireLogin: true
        }).when('/metaconfig', {
            templateUrl: '/content/...',
            controller: 'Ctrl2',
            requireLogin: true

...

Then Whenever a route gets requested I have this function that evaluates whether the route requires specific privileges/authentication to be in that route or not (ofc this does not mean that your route is secured, it simply means that your average joe wont be able to view routes unless js files get modified on the fly)

App.js

angular.module('App', ['ngCookies', 'ngRoute'])
.config(function{...}).run(function ($rootScope, ..., $location, authService) {


    $rootScope.loginStatus = false;
    authService.authCheck();

    $rootScope.$on('$routeChangeStart', function (event, next, current) {
         if (next.requireLogin) {
             if (!authService.isLoggedIn()) {
                $location.path('/login');
                event.preventDefault();
              }
           }
       });

The function passed with $routeChangeStart calls another function inside a service I have called authService and it looks like this

authService.js

'use strict';


App.service('authService', function ($rootScope, $log, $http, $q) {

    var userIsAuthenticated = false;

    this.auth = function (up, cb) {
        $http({method: 'POST', url: '/api/login', data: up}).
            success(function (data, status, headers, config) {
                if (status === 200) {
                    userIsAuthenticated = true;
                }
                cb(status)
            }).
            error(function (data, status, headers, config) {
                userIsAuthenticated = false;
                cb(status)
            });
    };
    this.authCheck = function () {
        $http({method: 'PUT', url: '/api/login' }).
            success(function (data, status, headers, config) {
                if (status === 200) {
                    userIsAuthenticated = true;
                } else {
                    userIsAuthenticated = false;
                }
                $rootScope.loginStatus = userIsAuthenticated;
            }).
            error(function (data, status, headers, config) {
                userIsAuthenticated = false;
                $rootScope.loginStatus = userIsAuthenticated;
            });
    };
    this.isLoggedIn = function () {
        return userIsAuthenticated;
    };
    this.logout = function (cb) {
        $http({ method: 'DELETE', url: '/api/logout' }).
            success(function (data, status, headers, config) {
                userIsAuthenticated = false;
                cb(status)
            }).
            error(function (data, status, headers, config) {
                userIsAuthenticated = false;
                cb(status)
            });
    };
});

Now every time a route that requires specific role or authentication will need to evaluate and get a 200 status code from the server otherwise the authentication status is set to false until the user authenticates again. Finally, relying on your front-end on even 1% of the auth process would make the whole auth system redundant.


Edit

CSRF risk and session management

In the case of CSRF, I am using a framework called Beego, this framework offers CSRF avoidance mechanism out of the box where you just need to specify expatriation date and encryption of the token.

When it comes to the login sessions, the server should manage encrypted sessions that are stored as cookies on the client side, of course I do not recommend you implement this as it is very risky and can be implemented in a wrong way. Again, Beego offers a nice feature that allows you to manage and encrypt sessions in your own way

I hope this helps answer your question, good luck.

like image 115
ymg Avatar answered Oct 22 '22 03:10

ymg