Retrieving tokens from OAuth2 authorize endpoint in angularjs - javascript

I am trying to use IdentityServer with an Angularjs application. The Angularjs front end is meant to call the authorize endpoint when an unauthenticated user attempts to navigate to the root url of the application. The app.config has a resolve property which will generate a routeChangeError. This event triggers a redirect to the OAuth2 Authorization endpoint (IdentityServer in my case, running on Asp.Net MVC).
baseballApp.config(['$routeProvider', '$locationProvider', '$httpProvider', function($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when('/players', {
resolve: {
auth: function ($q, $rootScope) {
var deferred = $q.defer();
if ($rootScope.token) {
deferred.resolve();
} else {
deferred.reject();
}
return deferred.promise;
}
},
templateUrl: '/FrontEnd/Templates/PlayerList.html',
controller: 'PlayerListController'
});
$routeProvider.otherwise({ redirectTo: '/players' });
var spinnerFunction = function (data, headersGetter) {
if (data) {
$('#spinner').show();
return data;
}
};
$httpProvider.defaults.transformRequest.push(spinnerFunction);
$httpProvider.interceptors.push('authInterceptor');}]).run(['$rootScope', '$location', 'authenticationService', function ($rootScope, $location, authenticationService) {
//authenticationService.getBearerToken().then(function(data) {
// $rootScope.token = data;
//});
debugger;
var generateRandomString = function() {
return (Math.random().toString(16) + "000000000").substr(2, 8);
}
var nonce = generateRandomString();
var state = generateRandomString();
$rootScope.$on('$routeChangeError', function () {
window.location = 'https://localhost:44301/identity/connect/authorize?client_id=baseballStats&redirect_uri=https%3a%2f%2flocalhost%3a44300%2f&response_type=id_token+token&scope=openid+profile+roles+baseballStatsApi&nonce=' + nonce + '&state=' + state;
});}]);
As you can see I redirect to the authorize endpoint if I don't have a token. However, it's not clear to me how I can retrieve the tokens after I have signed in on the authorization endpoint.
I have specified a return url, and after sign in it does redirect to the application, but how would I set up my angular code to retrieve the token that is supposed to be generated on the Authorize endpoint and sent back to the application?
I have seen many examples of people using bearer token authentication for angular, but I have not seen anyone use the Authorize endpoint of an OAuth2 server with Angular. In MVC, there are many built in callbacks to retrieve the token. I am looking for some ideas on how to implement this in angular.
Could you please give me a hint as to what I'm doing wrong?
EDIT
I have also tried to pass a hash navigation tag in the redirect uri, but get an "Error unknown client" from IdentityServer. Please see the code below
$rootScope.$on('$routeChangeError', function () {
var returnUrl = encodeURIComponent('https://localhost:44300/#/players');
window.location = 'https://localhost:44301/identity/connect/authorize?client_id=baseballStats&redirect_uri=' + returnUrl + '&response_type=id_token+token&scope=openid+profile+roles+baseballStatsApi&nonce=' + nonce + '&state=' + state;
});
Anybody know why?

I presume you're using the implicit flow grant for your spa js app?
In that case, the identity server, after you've logged in, will send the access token back to a call back url of your spa app. Using that call back page you are able to capture to token in order to pass it along to your resource server within the authorization header as a bearer token.
Checkout this video from Brock Allen, very well explained:
https://vimeo.com/131636653
By the way, you should use the oidc-token-manager, written by Brock himself. It is very easy to use and it abstracts away all communication with the identity server. It's also available on bower so you can grab it from there.
Finally I would like to point you to my own spa angular app I've been working on lately: https://github.com/GeertHuls/SecuredApi.
It actually uses the token manager in order to obtain access tokens, auto-refresh them an expose them throughout the entire angular app.
Hope this will inspire you a little in order to integrate this with angular.

I sidestepped around this issue by creating a custom ActionResult which would return a JSON response with a token when the user was already logged in via .NET Auth cookies, it's a bit hacky and looks like this;
[AllowAnonymous]
public ActionResult GetToken()
{
if (!User.Identity.IsAuthenticated)
{
Response.StatusCode = 403;
return Json(new {message = "Forbidden - user not logged in."}, JsonRequestBehavior.AllowGet);
}
var claims = new ClaimsPrincipal(User).Claims.ToArray();
var identity = new ClaimsIdentity(claims, "Bearer");
AuthenticationManager.SignIn(identity);
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var token = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
return Json(new { token }, JsonRequestBehavior.AllowGet);
}

Related

How to use different API's with different Authentication headers in the same Angular JS App

Hello I'm just a AngularJs beginner and want some advice/help from u guys!
I am making an app where you can login and check your current location in the building through the Cisco CMX server.
Its a server which calculates your position through the info of several access points you are connected with
I need to communicate with the mongodb for authenticating the users and I use a token for that.
And when I'm logged in I want to go to the API of the CMX with another authentication header.
But I cant see how it could work.
I set my default header in my app.js on run
$http.defaults.headers.common['Authorization'] = 'Bearer ' + $window.jwtToken;
And when I want to go to the CMX API i change my default header
$http.defaults.headers.common['Authorization'] = 'Basic AAAAAAAAAAAAAAAAAAAAA==';
But it wont do the trick..
Isn't it better to communicate with the CMX through the webserver itself instead of the client?
Thanks
In your case you want to intercept every HTTP request and inject it with an Authorization header containing a custom token.
to do that you need to use the angularjs Interceptors.
The interceptors are service factories that are registered with the
$httpProvider by adding them to the $httpProvider.interceptors array.
The factory is called and injected with dependencies (if specified)
and returns the interceptor
Here is an example of an interceptor that injects a token if it’s available in browser’s local storage.
$httpProvider.interceptors.push(['$q', '$location', '$localStorage', function ($q, $location, $localStorage) {
return {
'request': function (config) {
config.headers = config.headers || {};
if ($localStorage.token) {
config.headers.Authorization = 'Bearer ' + $localStorage.token;
}
return config;
},
'responseError': function (response) {
if (response.status === 401 || response.status === 403) {
$location.path('/signin');
}
return $q.reject(response);
}
};
}]);
code inspired from

Whats the correct format to send an authorisation header in a http request in AngularJS [duplicate]

I want to create an example for authentication and authorization in an SPA angularjs application using asp.net mvc webapi as the backend and client side routing (no cshtml). Below is just example of functions that can be used to set up the complete example. But I just can´t put it all togehter. Any help appreciated.
Questions:
What is best practise: Cookie or Token based?
How do I create the bearer token in angular to authorize on each request?
Validation on API functions?
How do I preserve the autentication signed in user on the client?
Example code:
Sign in form
<form name="form" novalidate>
<input type="text" ng-model="user.userName" />
<input type="password" ng-model="user.password" />
<input type="submit" value="Sign In" data-ng-click="signin(user)">
</form>
Authentication Angular Controller
$scope.signin = function (user) {
$http.post(uri + 'account/signin', user)
.success(function (data, status, headers, config) {
user.authenticated = true;
$rootScope.user = user;
$location.path('/');
})
.error(function (data, status, headers, config) {
alert(JSON.stringify(data));
user.authenticated = false;
$rootScope.user = {};
});
};
My API backend API Code.
[HttpPost]
public HttpResponseMessage SignIn(UserDataModel user)
{
//FormsAuthetication is just an example. Can I use OWIN Context to create a session and cookies or should I just use tokens for authentication on each request? How do I preserve the autentication signed in user on the client?
if (this.ModelState.IsValid)
{
if (true) //perform authentication against db etc.
{
var response = this.Request.CreateResponse(HttpStatusCode.Created, true);
FormsAuthentication.SetAuthCookie(user.UserName, false);
return response;
}
return this.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Invalid username or password");
}
return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState);
}
Authorization
Using the JWT library for restricting content.
config.MessageHandlers.Add(new JsonWebTokenValidationHandler
{
Audience = "123",
SymmetricKey = "456"
});
My API methods
[Authorize]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Whether to use cookie authentication or (bearer) tokens still depends on the type of app you have. And as far as I know there aren't any best practice yet. But since you are working on a SPA, and are already using a JWT library, I would favor the token based approach.
Unfortunately, I cannot help you with ASP.NET, but usually JWT libraries generate and verify the token for you. All you have to do is call generate or encode on the credentials (and the secret) and verify or decode on the token sent with every request. And you don't need to store any state on the server and don't need to send a cookie, what you probably did with FormsAuthentication.SetAuthCookie(user.UserName, false).
I'm sure your library provides an example on how to use generate/encode and verify/decode tokens.
So generating and verifying is not something you do on the client side.
The flow goes something like this:
Client sends the user provided login credentials to the server.
Server authenticates credentials and responds with a generated token.
Client stores the token somewhere (local storage, cookies, or just in memory).
Client sends the token as an authorization header on every request to the server.
Server verifies the token and acts accordingly with either sending the requested resource, or an 401 (or something alike).
Step 1 and 3:
app.controller('UserController', function ($http, $window, $location) {
$scope.signin = function(user) {
$http.post(uri + 'account/signin', user)
.success(function (data) {
// Stores the token until the user closes the browser window.
$window.sessionStorage.setItem('token', data.token);
$location.path('/');
})
.error(function () {
$window.sessionStorage.removeItem('token');
// TODO: Show something like "Username or password invalid."
});
};
});
sessionStorage keeps the data as long as the user has the page open. In case you want to handle expiration times yourself, you could use localStorage instead. The interface is the same.
Step 4:
To send the token on every request to the server, you can use what Angular calls an Interceptor. All you have to do is get the previously stored token (if any) and attach it as a header to all outgoing requests:
app.factory('AuthInterceptor', function ($window, $q) {
return {
request: function(config) {
config.headers = config.headers || {};
if ($window.sessionStorage.getItem('token')) {
config.headers.Authorization = 'Bearer ' + $window.sessionStorage.getItem('token');
}
return config || $q.when(config);
},
response: function(response) {
if (response.status === 401) {
// TODO: Redirect user to login page.
}
return response || $q.when(response);
}
};
});
// Register the previously created AuthInterceptor.
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('AuthInterceptor');
});
And make sure to always use SSL!

How do you show that the user is authenticated after they authenticate with Facebook?

I'm following this tutorial, implementing Facebook authentication with Node and Passport.
Both the tutorial and the docs say to do this:
router.get('/auth/facebook/callback',
passport.authenticate('facebook', {
successRedirect: '/',
failureRedirect: '/login'
})
);
But then how does the user know whether or not they're authenticated? I'd like to send data to my front end that shows whether or not the user is logged in, and that I could use to update, say my navbar.
From what I understand, the process of authenticating with Facebook works like this:
Send out GET /auth/facebook request.
Hits route.
304 Redirect send back with url to the Facebook portal.
Send out request to Facebook portal with callback url.
User clicks "accept" on the portal.
Facebook sends back a 304 Redirect with the url being your callback url.
Request is sent out to your callback url, which in this case is GET /auth/facebook/callback.
The cb of passport.use(obj, cb) gets run.
A 304 Redirect is sent back, with the url being / or /login depending on success or failure.
A request is sent out to / or /login.
I'm not sure how I could update my front end with the logged in user.
router.get('/auth/facebook/callback',
passport.authenticate('facebook'),
function(req, res) {
var userCopy = JSON.parse(JSON.stringify(req.user));
delete userCopy.auth;
res.status(200).json(userCopy);
}
);
This doesn't work because there isn't a $http.get().then() that receives the data.
I tried adding in some query parameters:
successRedirect: '/?foo=bar'
But it's not working for me. The url bar says http://localhost:3000/?foo=bar#_=_ and this doesn't log anything:
app.get('/*', function(req, res) {
var url = path.resolve(__dirname + '/../client/' + envFolder + '/index.html');
res.sendFile(url, null, function(err) {
if (err) {
return res.status(500).send(err);
}
console.log('here');
console.log(req.query);
return res.status(200).end();
});
});
It works when I do successRedirect: '/home?facebook=true'. But inside of sendFile, I can't send anything else back. If I try res.send('Authenticated with Facebook') it gives me this error: Error: Can't set headers after they are sent.. Presumably because sendFile is sending back the file and setting some headers, and then I try to send something else back with different headers. So I'm not sure what to do.
The approach I arrived at is to use the run block:
function run($http, Session, $cookies) {
$http
.get('/current-user')
.then(function(response) {
Session.setUser(response.data);
$cookies.put('userId', response.data._id);
})
;
}
I'm not sure that this is the best approach.
It messes my tests up because they're not expecting the HTTP request and it sets the Session and cookies when I don't always want to do that.
You could render the user object in the view, so you can get it via Angular checking the presence of the variable:
router.get('/auth/facebook/callback',
passport.authenticate('facebook'),
function(req, res) {
var userCopy = JSON.parse(JSON.stringify(req.user));
delete userCopy.auth;
res.status(200).render('index', {user: userCopy});
}
);
Your HTML index file should have:
<!--Embedding The User Object-->
<script type="text/javascript">
var user = <%= user | json | safe %>;
</script>
And finally your AngularJS app should have a service like this:
// Authentication service for user variables
angular.module('users').factory('Authentication', [
function() {
var _this = this;
_this._data = {
user: window.user
};
return _this._data;
}
]);
Inject Autenthication service in your main controller and play with the user object:
angular.module('core').controller('MainController', ['$scope', 'Authentication',
function ($scope, Authentication) {
$scope.authentication = Authentication;
}
]);

Angular Auth against Laravel backend

I am creating an app using Laravel and building a small internal API to connect to with an Angular frontend.
I have the auth working, but wanted to ensure that this is an acceptable way to log in a user, and to make sure everything is secure.
Sessions Controller:
public function index() {
return Response::json(Auth::check());
}
public function create() {
if (Auth::check()) {
return Redirect::to('/admin');
}
return Redirect::to('/');
}
public function login() {
if (Auth::attempt(array('email' => Input::json('email'), 'password' => Input::json('password')))) {
return Response::json(Auth::user());
// return Redirect::to('/admin');
} else {
return Response::json(array('flash' => 'Invalid username or password'), 500);
}
}
public function logout() {
Auth::logout();
return Response::json(array('flash' => 'Logged Out!'));
}
Laravel Route:
Route::get('auth/status', 'SessionsController#index');
Angular Factory:
app.factory('Auth', [ "$http", function($http){
var Auth = {};
Auth.getAuthStatus = function() {
$http({
method: "GET",
url: "/auth/status",
headers: {"Content-Type": "application/json"}
}).success(function(data) {
if(!data) {
console.log('Unable to verify auth session');
} else if (data) {
console.log('successfully getting auth status');
console.log(data);
// return $scope.categories;
Auth.status = data;
return Auth.status;
}
});
}
return Auth;
}
]);
I would then essentially wrap the whole app in something like an "appController" and declare the 'Auth' factory as a dependency. Then I can call Auth.getAuthStatus() and hide / show things based on the user state since this will essentially be SPA.
I realize I also need to hide the /auth/status URI from being viewed / hit by anyone, and was wondering how to do that as well. Kind of a general question but any insight would be greatly appreciated. Thanks.
Great question. I've answered this same question before so I will say the same thing.
Authentication is a bit different in SPAs because you separate your Laravel app and Angular almost completely. Laravel takes care of validation, logic, data, etc.
I highly suggest you read the article linked at the bottom.
You can use Laravel's route filters to protect your routes from unauthorized users. However, since your Laravel application has now become an endpoint only, the frontend framework will be doing the heavy lifting as far as authentication and authorization.
Once you have route filters set, that doesn't prevent authorized users from attempting to do actions that they are not authorized to do.
What I mean by the above is for example:
You have an API endpoint: /api/v1/users/159/edit
The endpoint is one of the RESTful 7, and can be used to edit a user. Any software engineer or developer knows that this is a RESTful endpoint, and if authorized by your application, could send a request with data to that endpoint.
You only want the user 159 to be able to do this action, or administrators.
A solution to this is roles/groups/permissions whatever you want to call them. Set the user's permissions for your application in your Angular application and perhaps store that data in the issued token.
Read this great article (in AngularJS) on how to authenticate/authorize properly using frontend JavaScript frameworks.
Article: https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec

Angular - Loading a view without changing the url

I'm working on a project which has several views, which some of the views may not be accessible to a given user. What I want to do is that, if a user navigates to a url that is restricted (due to his permissions), lets say '/project/manhatten/security' I want to display a view (an html partial) which simply says 'Access Denied'.
But I want to display the view without changing the url. I want the url to stay '/project/manhatten/security', so the user can copy the url and give it to someone with enough permission, and it would work fine.
What is the best way to achieve this ? Can I still use ng-view or a combination of ng-view and ng-include ?
Thanks in Advance.
I don't know of a way on how to restrict access to a specific view in angular. I think you shouldn't restrict views. What you should do is restrict access to your api. So if a user doesn't have the privilege to delete a project. Simply send a 401 from the server when he calls the api. On the client side handle this 401 in angular with an $http interceptor.
I would do the following:
In your index.html create an element/directive to display the error message
index.html
<div ng-controller=ErrorMessageCtrl ng-show=error.message>
{{error.message}}
<ng-view></ng-view>
The ErrorMessageCtrl will get notified when an access denied error occured:
.controller('ErrorMessageCtrl', function ($scope) {
$scope.error = {}
$scope.$on('error:accessDenied', function(event, message) {
$scope.error.message = message
})
})
Create an interceptor service to handle http 401 auth error:
.factory('authErrorInterceptor', function ($q, $rootScope) {
return {
response: function (response) {
return response
},
responseError: function(rejection) {
if (rejection.status === 401) {
$rootScope.$broadcast('error:accessDenied', 'Access Denied')
}
return $q.reject(rejection)
}
}
})
add the interceptor service to $httpProvider
.config(function ($httpProvider, $routeProvider) {
$httpProvider.interceptors.push('authErrorInterceptor')
$routeProvider.when('/project/manhatten/security', {
template: '<div><h1>Secure Page</h1>secure data from server: {{data}}</div>',
controller: 'SecureDataCtrl'
})
})
$http.get('/api/some/secure/data') returns a 401 http status code
.controller('SecureDataCtrl', function ($scope, $http) {
$scope.data = 'none'
$http.get('/project/manhatten/security')
.success(function (data) {
$scope.data = 'secure data'
})
})
Please keep in mind that this was hacked in 10 minutes. You need to do some refactoring. Like creating an errorMessage directive which gets the error notification service injected and not broadcasting the error message to the scope.

Categories

Resources