5

I'm using Laravel and Angular to write a web app.

In the front end Laravel is used to create the basic template, but otherwise controlled by Angular. In the back end laravel is used to create a restful API.

I have a few routes like this:

Route::group(['domain' => 'domain.com'], function() {

    Route::get('/', ['as' => 'home', function () {
        return view('homepage');
    }]);

    Route::get('/login', ['as' => 'login', function () {
        return view('login');
    }]);

    //users should be authenticated before accessing this page
    Route::get('/dashboard', ['as' => 'dashboard', function () {
        return view('dashboard');
    }]); 

});

Route::group(['domain' => 'api.domain.com', 'middleware' => ['oauth']], function() {
    Route::post('/post/create', ['uses' => 'PostController@store']);
    Route::get('/post/{id}', ['uses' => 'PostController@show']);

     //other API endpoints
     // ...
});

I want to make sure my domain.com/dashboard URL is only accessed by authenticated users.

In my backend I have OAuth implemented for my API routes which makes sure the user accessing those routes are authentic. Laravel's Auth::once() is used by the OAuth library to make sure the user credentials are correct then generates an access_token. Since Auth::once() is a "stateless" function no session or cookies are utilized and I cannot use Auth::check() to make sure a user is authenticated before the dashboard page is rendered.

How should I go about checking to see if the user trying to access domain.com/dashboard is authenticated? Should I send the access_token in the header when I forward the user from /login to /dashboard? Or should I implement Laravel's a session/cookie based authentication?


EDIT: As per this: Adding http headers to window.location.href in Angular app I cannot forward the user to the dashboard page with an Authorization header.

In order to reuse my API for my mobile apps I STRONGLY prefer to use some sort of token based authentication.

Community
  • 1
  • 1
Zaki Aziz
  • 3,592
  • 11
  • 43
  • 61

5 Answers5

4

I would advise to use JWT (JSON Web Tokens) to control for authentication.

I think there are several tutorials for their use with Lavarel and AngularJS. I'm more into Python and I use Flask, but the followings look interesting :

Pierre Cordier
  • 494
  • 3
  • 17
3

Pierre was right in suggesting JWT for your token based auth.

When the user successfully logs in, before you finish the request, you can create a JWT and pass that back to the client. You can store it on the client (localStorage, sessionStorage) if you want. Then on subsequent requests, put the JWT inside of your Authorization header. You can then check for this header in your middleware and prevent access to your API routes if the token is valid. You can also use that token on the client and prevent Angular from switching routes if the token doesn't exists or isn't valid.

Now if you are trying to prevent the user from accessing the page entirely on initial load (Opens browser, goes straight to domain.com/dashboard), then I believe that is impossible since there is no way to get information about the client without first loading some code on the page.

Mr_Antivius
  • 625
  • 6
  • 17
  • Would it be possible if I mixed a cookie based authentication with a session bases authentication? – Zaki Aziz Apr 13 '16 at 16:55
  • Truthfully, I have very little knowledge on cookies and sessions. From a quick Google search, it does appear that they will work on initial load. As I'm not behind my dev computer at the moment, I can't test it myself. But I'm sure you can spin something up and give it a try to see if it fits what you are looking for. – Mr_Antivius Apr 14 '16 at 08:32
  • Looking into this more, it seems like suggest creating a JWT and using a cookie as a storage for it. That way, the cookie is **always** sent and you can check the JWT to see if the user is authenticated. Since you need to use cookies, you will have to use Laravel's cookie/session implementation. From my understanding, cookies are best for server side rendering and routing, JWT is best for api and client-side auth. – Mr_Antivius Apr 18 '16 at 02:31
  • @Xecure I know - it's very necro. But i have stugled with same issue. It may be very usefull for future readers. Take a look at this example https://www.sitepoint.com/user-authentication-mean-stack/ .There is a also github repo for it. There is no coockies and sessions. Only JWT powered authentication with angular in front. Unfortunately I not familiar with angular, and i can not yet rewrite working example without angular. But in you case - it's may be thing you look for. – northernwind Apr 05 '17 at 12:54
2

Not sure about Angular, as I have never used it, but have you tried targeting a controller with your dashboard route? For example

Route::get('/dashboard', [
    'uses' => 'UserController@getDashboard',
    'as' => 'dashboard',
    'middleware' => 'auth'
]);

UserController.php (I'm assuming you have a blade called dashboard.blade.php)

<?php
namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class UserController extends Controller
{
    public function getDashboard()
    {
        if(Auth::user()) {
             return view('dashboard');
        } else {
             redirect()->back();
        }
    }
}

Also, you could always group whichever routes you want to protect with this (taken from the Laravel 5.2 documentation):

Route::group(['middleware' => 'auth'], function () {
    Route::get('/', function ()    {
        // Uses Auth Middleware
    });

    Route::get('user/profile', function () {
        // Uses Auth Middleware
    });
});

EDIT: Regarding the session token, your login blade should have this in its code:

 <input type="hidden" name="_token" value="{{ Session::token() }}">
FiddlingAway
  • 1,598
  • 3
  • 14
  • 30
1

When the /dashboard HTML loads, does it already include data specific to the user's account?

I would suggest to load the /dashboard HTML separately from the user's data, and hide the dashboard items with ng-cloak while Angular loads the data to populate it.

  1. Redirect to /dashboard URL
  2. Load (static?) dashboard HTML / Angular app, hiding all parts with ng-cloak.
  3. Have Angular access the API using the access_token to load all dashboard data.
  4. Revealing the parts of the dashboard when the data from the API comes in, or showing an error message if the access_token wan't valid.

That way, your /dashboard HTML could actually be just a static HTML file and directly served by the web server and cached by any proxy on the way.


If that isn't an option, you could put your access_token into a cookie with Javascript that runs on the /login view, then redirect to /dashboard, then have your server-side /dashboard view read the cookie to check if the access_token is valid. But that sounds messy and mixes up things that should be separated.

C14L
  • 12,153
  • 4
  • 39
  • 52
1

@Pierre Cordier @Mr_Antivius Thank you guys for your answer, it helped me get insight into the problem and allowed me to tinker with with JWT but ultimately did not product a solution for me.

To allow only authenticated users to access domain.com/dashboard I had to implement a hybrid session and OAuth authentication system. I decided to go with Sentinel (instead of Laravel's out of the box auth system) because it has a user permission system I need in other places in my app. I use this library for the OAuth Server.

Here is what I do in my controller:

POST domain.com/auth/authenticate:

public function processLogin(Request $request)
{
    $credentials = [
        'email'    => $request->input('username'),
        'password' => $request->input('password'),
    ];

    try
    {
        $sentinel = Sentinel::authenticate($credentials);
    } 
    catch (\Cartalyst\Sentinel\Checkpoints\ThrottlingException $e)
    {
        $response   = ['error' => [$e->getMessage()]];
        $httpStatus = 429;
        return response()->json($response, $httpStatus);
    } 
    catch (\Cartalyst\Sentinel\Checkpoints\NotActivatedException $e) 
    {
        $response   = ['error' => [$e->getMessage()]];
        $httpStatus = 401;
        return response()->json($response, $httpStatus);
    }

    if ($sentinel) //user credentials correct
    {
        //get oauth token
        $oauthToken = Authorizer::issueAccessToken();

        $response   = ['success' => true, 'user' => ['id' => $sentinel->id, 'email' => $sentinel->email]] + $oauthToken;
        $httpStatus = 200;
    } 
    else 
    {
        $response   = ['success' => false, 'error' => ['Incorrect credentials']];
        $httpStatus = 401;
    }

    return response()->json($response, $httpStatus);
}

Here is the method the OAuth library looks at to authenticate the user:

public function verifyAuth($email, $password)
{
    $credentials = [
        'email'     => $email,
        'password'  => $password,
    ];

    if ($user = Sentinel::stateless($credentials)) 
    {
        return $user->id;
    } 
    else 
    {
        return false;
    }
}

This would create a response like so:

{
  "success": true,
  "user": {
    "id": 1,
    "email": "email@domain.tld"
  },
  "access_token": "6a204bd89f3c8348afd5c77c717a097a",
  "token_type": "Bearer",
  "expires_in": 28800,
  "refresh_token": "092a8e1a7025f700af39e38a638e199b"
}

Hope this helps someone out there


Side Note: I'm sending a POST request to domain.com/auth/authenticate instead of api.domain.com/auth/authenticate because I could not get domain.com/dashboard to recognize sentinel's cookie if I posted to api.domain.com. I've tried changing domain in config/session.php to .domain.com but still nothing. Maybe I'm doing something wrong?

Zaki Aziz
  • 3,592
  • 11
  • 43
  • 61