Authentication Logic - Server vs Client - angularjs

I'm trying to get my head around where authentication logic should live in my application, the approach I am trying to take is to have any auth responsibility handled by the server, redirecting to a login page that's separate from the main client side app - which I think is sensible?
I have an angularjs application which uses ui-router and makes api requests which are routed via the server.
I am using an Express server which is configured to use a couple of directories like so:
app.use(express.static('./dist/client'));
app.use(express.static('public'));
I then have middleware that performs an auth check (Im using express-session as well) and redirecting to login when required.
//A request to '/login' will serve the login page
app.use('/login', function(req, res){
res.sendFile(path.join(__dirname+'/public/login.html'))
});
//This will listen for all requests
app.use(function(req, res, next) {
if (req.url!== '/auth/login' && !req.session.accessToken) {
res.redirect('/login');
return;
}
next();
});
On initial page load, when no session cookie exists, express immediately redirects to the login view as expected.
After logon, and the main application loads, if I then manually delete the cookie in the browser and perform a state change that requires an api request (in a state resolve), the server returns the login view but this gets rendered inside the ui-view component being used by ui-router, rather than a full redirect to /login by the server.
Also, if I navigate to a page (after deleting cookie) that does not perform an api request, that page is served back, I guess as its not covered by my app.use middleware that does the redirect.
I feel I'm missing something obvious here, can someone help me please?

One way to handle this, there are others:
Make the API server return a 401 (unauthorized) error if the user is not authenticated, rather than redirecting them to the login page.
Then, in a run block, add a $stateChangeError event handler to the $rootScope. This way, if an API request is made from an unauthenticated user, it will trigger the event handler. From there you can redirect the user to your login page:
angular.module('myApp').run(function($rootScope, $window) {
$rootScope.$on('$stateChangeError', function() {
$window.location.href = '/login';
});
});
I'm not sure if it makes sense to worry about the other scenario where you delete the cookie and navigate to a page that does not make any API requests. What is such a user going to gain? In this hypothetical scenario, they are already looking at a page in your app (that might have sensitive data or not). How did they get there to begin with?
You could use a similar event handler for the $stateChangeStart event that checks for the presence of the cookie and redirect if it's missing. But, you don't want to put code in your client that validates the cookie, b/c then any curious visitor could read that code and learn how to create a cookie to fool your server.

Related

Blank page after login using bookmarked authorization URL in IdentityServer4

We have discovered that our users very often for the first time visits our web application by browsing the direct URL of the OIDC client (https://oidcclienturl.com/), The ASP.NET Core OIDC authentication middleware kicks in and the user gets redirected back to Identityserver 4 login page.
Everything works fine but then they decide to add the (temporary? state, nonce, cookies...) authorization URL as a bookmark in their browser before entering their credentials and continuing back to the web application.
This causes an issue when the user later uses the bookmark in a new session. The login seem to actually work after entering valid user credentials even if the user uses an old authorization URL, but when the user gets redirected back to the web application they end up on a blank page (https://oidcclienturl.com/signin-oidc).
After the blank page have been loaded the user is able to browse the direct URL (https://oidcclienturl.com/) sucessfully and appear as an authentcated user in the web application.
Any ideas whats causing the blank page?
That blank page shouldnt exist, if I understand it correctly its the default callback path of the oidc authentication middleware in ASP.NET Core.
Unfortunately, the real-world problem of users bookmarking the login page isn't handled cleanly by OIDC, which requires the client app to initiate the login flow.
I've addressed this by adding a RegistrationClientId column to my user data table, which is the Identity Server ClientId corresponding to the client app that called IDS when the user account was created. In the client app configuration, we use the custom Properties dictionary to add a URI fragment:
new Client
{
ClientId = "some_client",
ClientName = "Some Client",
ClientUri = "https://localhost:5000",
Properties = new Dictionary<string, string>
{
{ "StartLoginFragment", "/Auth/StartLogin" }
}
// other config omitted
};
When a user logs in, an empty return URL indicates IDS wasn't called by a client app, so we use RegistrationClientId to query IClientStore, then we combine the ClientUri and StartLoginFragment URIs and use the resulting URI to redirect the user back to the client application.
Over in the client application, that endpoint kicks off the OIDC sign-in flow, and since the user is already signed-in on IDS, it comes right back to the correct location in the client app. The controller action looks like this:
[HttpGet]
public async Task StartLogin()
{
await acctsvc.SignOutAsync();
await HttpContext.ChallengeAsync("oidc",
new AuthenticationProperties()
{
RedirectUri = "/"
});
}
The call to SignOutAsync just ensures any client-app signin cookies are cleaned up. It's in our custom account service, but it just runs HttpContext.SignOutAsync on the usual "Cookies" and "oidc" schemes. Normally that would also result in a signout call to IDS, but the redirection by the subsequent ChallengeAsync replaces the pending signout call.
The downside is that the action is an HTTP GET meaning pretty much anyone could theoretically trigger this action. At most it would be an annoyance.
In the special case where your IDS is only handling auth for a single client, you can skip a lot of that -- if they land on the page with no return URL, just send them to your client app start-login endpoint straightaway, before they login.

How can i redirect to an Angular router link with oauth2 login?

I want to make an oauth2 login with Twitch on my website and I have an angular2 website and I'm working with router links.
When I want to log me in with twitch acc to say yes it is me and so everything is fine. Ok the end not xD
When i go to the twitch oauth2 for authorizing i need an redirectUri. My problem is now how can i make this in angular2? Because I can't type www.page.com/app/afterlogin/afterlogin.php or somethink like that.
I need this because I need from the user the access token, I dont want that he need to authorize himself x times.
Maybe this helps for helping me:
https://api.twitch.tv/kraken/oauth2/authorize?client_id=[client_id]&redirect_uri=http://www.page.com/app/AfterLogin/afterlogin.php&response_type=code&scope=user_read
I hope someone can help me with redirecting and some oauth2 logins :)
Let me assume a RESTful backend with Single Page Application front and answer the question. The process in general is like the following
Your SPA --> Your Server --> Your Provider --> Your Browser --> Your Provider --> Your Server --> Your SPA
Your SPA => initializes login and passess redirect_uri
Your Server => Stores redirect_uri in a cookie and sends request to
provider
Your Provider => Gets Success and Failure Urls and loads login page
to your browser
Your Browser => Loads the provider login page
Your Provider => Sends request to your server success or failure
handler
Your Server => Extracts the redirect_uri and redirects the browser
to it
Your SPA => Gets afterLoginUrl from redirect_uri and route the
user to it
Below are the steps to achieve this
When your front end sends the authentication request to your server,
append the redirect_uri. In that url, pass a afterLoginUrl query
parameter. That is used by your front end SPA to route the user to
the specific page that triggered the login. (i.e. If the request has
been triggered by a user trying to access
{base_uri}/profile/project/projects for example, it is a good
practice to route the user to this page rather than to the default
page that a normal login takes to like base_uri/profile/about). As
a result you will have a url that looks like the following.
`http://localhost:8080/oauth2/authorize/google?redirect_uri=http://localhost:4200/oauth2/redirect&afterLoginUrl=/profile/project/projects`
port 8080 being for the back end and 4200 for the front end.
Since you are using a RESTful service, you don't have a way by which you can save the redirect_uri on your server (since REST is stateless). Because of this you need to send it with the request you send to the provider as a cookie.
When the success is received from the provider, you will know which route of your SPA to hit by extracting the cookie you sent. Then you dedicate a route to handle your request from your own server (in my case oauth2/redirect) in your front end app.
On the component specified for the route in step 3 you will receive token and afterLoginUrl(if there is). You will have something like the following on the url
http://localhost:4200/oauth2/redirect?afterLoginUrl=/profile/project/projects&token={token value}
Verify your token, check whether or not there is afterLoginUrl and redirect to the route specified by afterLoginUrl if there is one or to the default profile page if there isn't.
I think a wonderful resource can be found here.
Authorization Code Grant flow is just one of several ways of how you can use OAuth2. It's not suited for applications running in a browser, because it requires a client secret which you cannot keep safe in a browser.
There is another flow - Implicit flow which is meant for JavaScript applications - you get an access token and/or ID token in a redirect URI - in the hash part (#...) so they don't get to a server. Then you can easily use any Angular route path as a redirect URI. So the redirect URL from OAuth2 server could look something like this:
http://example.com/myAngularApp/afterLogin#token=...
When you get to that URI, you just save the token and change the route to some real form.

Firebase $onAuth has wrong authData after $authWithOAuthRedirect from Facebook

I am trying to authenticate users of my Firebase (Angularfire) app with Facebook Login.
Everything works as expected when I authenticate with a pop-up window, but to support as many browsers as possible (Chrome on iOS doesn't support pop-ups, for e.g.) I want to fallback to authenticating with a redirect ($authWithOAuthRedirect).
I have confirmed my setting in Facebook are correct (my app ID and secret, for e.g.) but when I am redirected back to my app after Facebook authenticating with a redirect, $onAuth fires but I don't have my Facebook authData.
Instead, I have anonymous authData. For a bit of background; all users are authenticated anonymously if they are not otherwise authenticated (with Facebook, in this e.g.).
I can't see to find why this would be - the user should now be authenticated with Facebook, and have the Facebook authData.
Excepts of my code are below for some context:
Triggered when a user clicks the login button
function logIn () {
firebaseAuth
.$authWithOAuthRedirect('facebook', function (error) {
if (error) {
console.log(error);
}
});
}
$onAuth (inside my Angular app's run)
function run ($rootScope, firebaseAuth, sessionStore) {
$rootScope
.$on('$routeChangeError', function (event, next, prev, error) {
if (error === 'AUTH_REQUIRED') {
console.log(error);
}
});
$rootScope
.$on('$routeChangeSuccess', function (event, current, prev) {
$rootScope.title = current.$$route.title;
});
firebaseAuth
.$onAuth(onAuth);
function onAuth (authData) {
console.log(authData);
}
}
Route resolver to otherwise anonymously authenticates users
function sessionState ($q, firebaseAuth) {
var deferred = $q.defer();
firebaseAuth
.$requireAuth()
.then(deferred.resolve, guest);
return deferred.promise;
function guest () {
firebaseAuth
.$authAnonymously()
.then(deferred.resolve, rejected);
}
function rejected () {
deferred.reject('AUTH_REQUIRED');
}
}
The route resolver (sessionState) checks to see if the user is authenticated already, and if not, tries to anonymously authenticate them.
After the Facebook authentication redirect, the user will already be authenticated, and therefore does not need to be anonymously authenticated.
But, it appears that they are? As $onAuth logs the authData to the console, and it is anonymous.
Any help with this would be much appreciated! I am sure it has something to do with my route resolver, as pop-up authentication works fine (the route is already resolved).
EDIT: I tried completely removing my route resolver in case it was that causing an issue, but it made no difference. The user was just 'unauthenticated' instead of being either authenticated with Facebook (after $authWithOAuthRedirect) or anonymously.
UPDATE: I tried authenticating with Twitter and the redirect transport and I have encountered the exact same problem. I have also tried using port 80, instead of port 3000 that my app was being served on locally, but no joy.
UPDATE: When I turn off html5Mode mode in my app - and routes now begin with #s - $authWithOAuthRedirect works perfectly. From this I can only assume that $authWithOAuthRedirect does not support AngularJS's html5Mode. Can anyone confirm this is an issue, or do I need to change my code to support html5Mode and authWithOAuthRedirect?
EXAMPLE REPO Here is an example repo demonstrating the problem: https://github.com/jonathonoates/myapp
Look in the dist directory - you should be able to download this and run the app to reproduce the problem. In scripts/main.js is the app's JS; I've added a couple of comments but it's pretty self explanatory.
To reproduce the problem: click on the 'Facebook Login' button, and you'll be redirected to Facebook to authenticate. FB will redirect you back to the app, but here lies the problem - you won't be authenticated, and the returned authData will be null - you'll see this in the console
UPDATE: When I add a hashPrefix in html5Mode e.g.
$locationProvider
.html5Mode(true)
.hashPrefix('!');
The app works as I would expect - authenticating with Facebook and the redirect transport works.
Couple of niggles though:
The URL has #%3F appended to it, and is available/visible in the browser's history.
This would rewrite URLs with #! in browsers that do not support History.pushState (html5Mode), and some less advanced search engines might look for a HTML fragment because of the 'hashbang'.
I'll look into highjacking the URL upon being redirected back from Facebook instead of using hashPrefix. In the URL there is a __firebase_request_key which may be significant e.g.
http://localhost:3000/#%3F&__firebase_request_key=
It looks like this is indeed an incompatibility between Firebase and AngularJS's html5mode as you suspected. At the end of the redirect flow, Firebase was leaving the URL as "http://.../#?", and Angular apparently doesn't like that so it did a redirect to "http://.../" This redirect interrupts Firebase (the page reloads while we're trying to auth against the backend) and so it is unable to complete the authentication process.
I've made an experimental fix that ensures we revert the URL to http://.../#" at the end of the redirect flow, which Angular is happy with, thus preventing the problematic redirect. You can grab it here if you like: https://mike-shared.firebaseapp.com/firebase.js
I'll make sure this fix gets into the next version of the JS client. You can keep an eye on our changelog to see when it is released.

how to handle passport-facebook callback in angular client?

I am developing a MEAN application. I am using passport for authentication- local, facebook and google strategies.
I am using angularjs client. All the routing is handled at client. I am only consuming server data apis.
When using passport-facebook strategy, I am using below code at node server as per passport docs.
app.get('/auth/facebook',passport.authenticate('facebook-auth', { scope : ['email'] }));
app.get('/auth/facebook/callback',passport.authenticate('facebook-auth', {
successRedirect : '/home',
failureRedirect : '/login',
scope:['email']
}));
Problem I am facing is when user click on "Sign in using Facebook" button
<i class="fa fa-facebook"></i> Sign in using Facebook
Client will access "/auth/facebook" route that will eventually redirect user to facebook page for validating user's credentials.
After successful validation, user will be redirected to route "/home" as defined in "successRedirect" value.
Now the thing is, I want to use custom callback function instead of defining redirects for success or failure. It will look like below:
app.get('/auth/facebook/callback',passport.authenticate('facebook-auth', function(err,user,info){
if(err){
throw err;
}
else if(user === 'userexists'){
res.json({
'state':false,
'message':'User with this e-mail already exists'
});
}
else{
req.logIn(user,function(loginErr){
if(loginErr){
throw loginErr;
}
res.json({
'state':true,
'message':'You logged in successfully!'
});
});
}
}));
The root problem I am facing here, I can not use above custom callback as Client is not calling the "auth/facebook/callback" route, it is called by facebook.
So, there is no success handler waiting to catch above callback's response at client side!!
I want some way to get response in json form at client to eliminate server side redirection and also way to pass message and username to client after successful authentication by facebook.
I am about to give up with passport. Hoping for any possible solution before removing a lot of code!
Thanks
This can be accomplished by redirecting to another endpoint inside the facebook callback handler. There is no need to do res.json() on the callback from facebook since they only make a request to that in order to let you know if auth failed or succeeded. From their docs:
// GET /auth/facebook/callback
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
So facebook returns control over request process back to you when they call /auth/fb/callback but it's up to you what to do next. Since once the user is successfully authenticated, you would have req.user available throughout the whole session. At this point, you can redirect to something like the have in the example /account and check if req.user with req.isAuthenticated() and complete the flow you desire.

Integrating Laravel, Facebook and Angular with token-based-authentication

My use case:
User is already logged in automatically server side using Facebook with laravel and Socialite.
I check if the user exists in the db and if not create it and log them into laravel.
Then I need to start an Angular app and make sure they are logged in with Laravel / Facebook.
After reading this article, it looks like this token based approach is what I should take.
In the tutorial you serve a login form with Angular, and then pass the email and password to an authenticate controller (Laravel), which returns a token (create by JWT-Auth).
Once the token is in Angular all is well, my problem is that I want to get the token directly into Angular without a login form since the user is already logged in as mention above.
I'm wondering if I could just output the token in markup somewhere and then pick it up from Angular or would that somehow be a security risk? I guess people will only be able to view source and see their own token?
If this is the wrong way to do this, then how should I do it? Do I need to authenticate with Facebook with Javascript, and then create a new laravel user with ajax?
Thanks so much!
One approach you could take is to add something to your .config block that checks for the presence of a JWT in local storage and if there isn't one there, makes a request to the API to see if the user is logged in on the Laravel side. If the user is logged in, a JWT is returned which can be picked up and saved in local storage. Since the .config block is run when the Angular app loads, this will only happen once, which is probably what you're looking for.
Here's what that might look like. First the Laravel side:
// AuthenticateController.php
...
// Simulates a user being logged in on the Laravel side through something
// other than credentials sent from the front-end. Obviously you would use
// Socialite
public function authenticate()
{
$user = User::find(1);
$token = JWTAuth::fromUser(1);
return response()->json(compact('token'));
}
...
Then the Angular:
// app.js
...
.run(function($rootScope, $state, $auth) {
if(!$auth.isAuthenticated()) {
// No credentials provided because the user is already logged in on the server
$auth.login().then(function() {
$state.go('users');
}
}
$rootScope.$on('$stateChangeStart', function(event, toState) {
...
This would just run when the Angular app loads, which will be after your user has logged in with Socialite. It's essentially just checking whether there is a user logged in on the back end and if so, the token is retrieved. It's a bit hacky and the interface isn't obvious, so it might not be the best solution.
You would need to arrange your authenticate controller on the Laravel side such that it returns the token if the user has logged in via socialite or require credentials if he/she hasn't (which is the traditional login).
You could also look at this approach to see if it works for you: https://github.com/barooney/jot-bot/tree/socialite
Let me know if that works out!

Resources