USF Service for CAS backed Token Authentication
For Angular ~1.4.x use
bower install https://github.com/jamjon3/UsfCAStokenAuth.git#1.0.0 --save
For Angular ~1.3.x use
bower install https://github.com/jamjon3/UsfCAStokenAuth.git#0.0.31 --save
In a Yeoman angular app, that would be in the 'app.js' right above the "config" section. To add this module 'UsfCAStokenAuth', just add it to the array of modules that looks something like:
angular
.module('exampleModuleUsingPlugin', [
'ngCookies',
'ngResource',
'ngSanitize',
'ngRoute',
'UsfCAStokenAuth'
])
Beginning with version 1.0.0, configuring you app with this constant is optional. Prior to 1.0.0, it's mandatory.
In a Yeoman angular app, the constant would be placed the 'app.js' right above the "config" section.
The plugin has some default routes it uses that you can override by providing key/value pairs.
Here's an example with the options you can override the built in defaults.
.constant('UsfCAStokenAuthConstant',{
'unauthorizedRoute': '/unauthorized',
'logoutRoute': '/logout',
'loginRoute': '/login',
'debug': false
})
Note: You can turn debugging on by setting the 'debug' key to true which will send debugging output to the console log
Your token services needs to be wired up as a "constant" in your application. This is done by defining the 'UsfCAStokenAuthConstant' with a unique id value on the 'applicationUniqueId' key (can be any arbitrary string) along with assigning key/value pairs on the "applicationResources" key.
Here's an example with a service defined as "exampleResource" and it's service URL:
.constant('UsfCAStokenAuthConstant',{
'applicationUniqueId': 'f6765e988eb32cbda5dcd9ee2673c0a8',
'applicationResources': {
'exampleResource': 'https://somecompany.com/~jdoe/ExampleApp/services.php'
},
'unauthorizedRoute': '/unauthorized',
'logoutRoute': '/logout',
'loginRoute': '/login',
'debug': false
})
This will produce a local storage binding to track tokens and their corresponding token servers with this defined service. Of course, when you define your 'own' services, you can reference this URL conviently while passing it into the function as 'applicationResources'.
Note: You can turn debugging on by setting the 'debug' key to true which will send debugging output to the console log
You may find the certain requests are not authorized. You need to wire this to a view to let the end user know. In the example constants above, the 'unauthorizedRoute' is defined as 'unauthorized'. That means you'll need to create a route for it and wire it into your routes.
In Yeoman, you can do that by typing:
yo angular:route unauthorized
You'll need to edit your view and anything you want in the controller it creates. Before the redirect, 'authorizedError' and 'unauthorizedRole' will be updated in the $rootScope so you can access it in the view and such (if desired).
Optional: If you specify a 'loginRoute', you will be redirected to that route on your first login. In the example constants above, the 'loginRoute' is defined as 'login'. That means you'll need to create a route for it and wire it into your routes. Note: You can bypass using this and just allow the first tokenAuth protected service trigger a login (if you don't care to redirect to a route, it will simply refresh the current page).
In Yeoman, you can do that by typing:
yo angular:route login
The plugin injects a function in the root scope you can access with a logout button called 'tokenAuthLogin'. This function needs to be called on clicking the button. That can look something like this in the code:
<button class="btn btn-sm" ng-click="tokenAuthLogin()">Login</button>
..or..
<a ng-href="" ng-click="tokenAuthLogin()">Login</a>
IMPORTANT: This function simply redirects to the route you specified. You're controller on that route will need to have a token protected function that will trigger the actual login.
You'll need a route to handle logout. You create the route and tell the plugin where to go after it clears it's session(s) with the token server(s). In the example constants above, the 'logoutRoute' is defined as 'logout'. That means you'll need to create a route for it and wire it into your routes.
In Yeoman, you can do that by typing:
yo angular:route logout
The plugin injects a function in the root scope you can access with a logout button called 'tokenAuthLogout'. This function needs to be called on clicking the button. That can look something like this in the code:
<button class="btn btn-sm" ng-click="tokenAuthLogout()">Logout</button>
..or..
<a ng-href="" ng-click="tokenAuthLogout()">Logout</a>
CORS has already been activated by the plugin. You can override this in $http or $resource. This is necessary to access the Token Server across domains, at least. Here's the settings in the plugin's config section:
if ('defaults' in $resourceProvider) {
// Only supported in 1.3.x
$resourceProvider.defaults.stripTrailingSlashes = false;
}
$httpProvider.defaults.useXDomain = true;
$httpProvider.defaults.withCredentials = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
One thing to notice is the delete of the header "X-Requested-With" at the end. This will bypass "Preflight" with an "Options" request.
The plugin was rewritten to make configuration optional. You can inject the 'tokenAuth' service when you need to access any it's internal methods (particularly for login and logout). The config object in $http and $resource calls to look for a 'tokenKey' (which is any arbitrary string you want) so any such requests will be handled as webtoken requests. Here's an example of how to make $http and $resource requests handled as webtoken requests:
angular.module('angCorstokenApp')
.factory('exampleService',['$resource','tokenAuth', function ($resource,tokenAuth) {
var ExampleResource = $resource('https://somecompany.com/~jdoe/ExampleApp/servicesOther.php',{},{
'list': {
method: 'POST', params: {'service': 'list'},responseType: 'json',tokenKey: 'exampleKey' }
}
});
var ExampleHttp = $http({ url: 'https://somecompany.com/~jdoe/ExampleApp/servicesOther.php', tokenKey: 'exampleKey', method: 'POST', params: {'service': 'list'},responseType: 'json' })
// Public API here
return {
list: function () {
return ExampleResource.list({}).$promise;
},
exampleHttp: function() {
return ExampleHttp;
}
};
}]);
You'll notice the key tokenKey: 'exampleKey'
. This tells the plugin to handle the request URL as a token request. If you have services
on different 'AuthTransfer' servers, you'll need to specify a separate 'tokenKey' for each server. Note: you do not need to specify separate tokenKey's
if your services are all on the same 'AuthTransfer' server. However, you will need to use the same unique tokenKey for each 'AuthTransfer' that's handling
your services.
Your service URL's are stored in the constant so you can easily refer to them by the applicationResource 'key' using a function from this plugin. You'll need to inject the 'tokenAuth' service into your service or controller similar to:
angular.module('angCorstokenApp')
.factory('exampleService',['$resource','tokenAuth', function ($resource,tokenAuth) {
var ExampleResource = $resource(tokenAuth.getResourceUrl('exampleResource'),{},{
'list': {
method: 'POST', params: {'service': 'list'},responseType: 'json' }
},
'listAgain': {
method: 'POST', params: {'service': 'list'},responseType: 'json',tokenKey: 'exampleResource', url:'https://somecompany.com/~jdoe/ExampleApp/servicesOther.php' }
}
});
var ExampleHttp1 = $http({ url: tokenAuth.getResourceUrl('exampleResource'), method: 'POST', params: {'service': 'list'},responseType: 'json' })
var ExampleHttp2 = $http({ url: 'https://somecompany.com/~jdoe/ExampleApp/services.php', method: 'POST', params: {'service': 'list'},responseType: 'json' })
var ExampleHttp3 = $http({ tokenKey: 'exampleResource', method: 'POST', params: {'service': 'list'},responseType: 'json' })
var ExampleHttp4 = $http({ url: 'https://somecompany.com/~jdoe/ExampleApp/servicesOther.php', tokenKey: 'exampleResource', method: 'POST', params: {'service': 'list'},responseType: 'json' })
// Public API here
return {
list: function () {
return ExampleResource.list({}).$promise;
},
exampleHttp1: function() {
return ExampleHttp1;
}
exampleHttp2: function() {
return ExampleHttp2;
}
exampleHttp3: function() {
return ExampleHttp3;
}
exampleHttp4: function() {
return ExampleHttp4;
}
};
}]);
There's a few things going on here. For one, 'tokenAuth' (which is a reference to this plugin's service) can be made available to this factory for accessing the 'getResourceUrl' function. In the 'list' method of 'ExampleResource' and 'ExampleHttp1' the URL for $resource and $http examples are obtained by the 'tokenAuth.getResourceUrl' function by passing the applicationResources 'key' for the service you provided in the constant you wired up for the plugin.
The 'ExampleHttp2' is using an explicit url but that url matches the value of the 'exampleResource' key and will be handled by the plugin similarly to 'ExampleResource' and 'ExampleHttp1'.
The 'ExampleHttp3' does not specify the URL but does specify an 'tokenKey'. The 'tokenKey' will be used to match the URL value and provide it to the $http service.
The 'ExampleHttp4' is a hybrid. Specifying the 'tokenKey' associates it with the token associated with it. The URL can be something different you know shares that token but is a different url. This allows you to have URL variations associated with the same token.
Finally, the 'listAgain' method of 'ExampleResource' uses the hybrid using both an explicit URL and 'tokenKey' associated with that token.
In summary, for $resource (or $http), you'll need to inject the 'tokenAuth' service into your service or controller to use the convience method. Then you can use the 'tokenAuth.getResourceUrl' for the URL but you can use different combinations. The plugin handles the rest and stores your tokens and such. Make sure your application has a unique 'applicationUniqueId' defined in your constant (this will keep your storage distinct from any other instances of applications using this plugin).
As long as a 'tokenKey' is not specified in the config of a $http or $resource request, the auth module is effectively bypassed.
If you have a http or resource service you want bypassed by this modules handling you can pass 'ignoreAuthModule' with a value of 'true' into the $http or $resource config. That may look something like:
$http({method: 'GET', url: '/someUrl', ignoreAuthModule: true }).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
The plugin creates a session cookie that will persist as long as the browser window is open. You can clear it using the 'tokenAuth.sessionLogout()' function. This will cause the application to clear out all tokens on reload which will make the user login again. This is useful to use on a "Logout" button as it will trigger the clearing of local storage tokens and begin a new session.
tokenAuth.clearToken('myAppKey');
(All versions) The 'clearToken' method will clear the associated token connected to the provided tokenKey.
tokenAuth.clearTokens();
(All versions) The 'clearTokens' method will clear all tokens on all tokenKeys.
tokenAuth.hasToken('myAppKey');
(All versions) The 'hasToken' method will return 'true' or 'false' if a token is currently stored corresponding to the provided tokenKey.
tokenAuth.clearLocalStorage();
(All versions) The 'clearLocalStorage' method clears ALL local storage
tokenAuth.sessionLogout();
(All versions) The 'sessionLogout' method clears out any existing session cookie and does a logout on the token server
tokenAuth.isLoggedIn();
(All versions) The 'isLoggedIn' method return true or false on the global login condition based on the existence of the session cookie
tokenAuth.getResourceUrl('myAppKey');
(pre 1.0.0 only) The 'getResourceUrl' method looks up the URL associated with the provided AppKey and returns it.
tokenAuth.isDebugEnabled();
(All versions) The 'isDebugEnabled' method checks to see if debugging is turned on in the constants declaration for the plugin.