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.
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 :
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.
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.
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 ?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.
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:
...
$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)
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
'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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With