Token-Based Authentication for AngularJS and Laravel Apps

Free Course

Getting Started with Angular 2

Angular 2 is the shiny new framework that comes with a lot of new concepts. Learn all the great new features.

Adding authentication to an AngularJS and Laravel application is not the most straight-forward, especially if we take the approach of creating independent front-end and backend applications and connecting them with an API exposed by Laravel. Laravel comes with easy-to-use authentication out of the box, but it is session-based and is therefore most useful for traditional round-trip applications.

For single page applications that rely on an API, a better way to handle authentication is with JSON Web Tokens, or JWTs. Put simply, a JWT (pronounced jot) is a JSON object with three distinct parts that are used together to convey information between two parties. JWTs consist of a header, a payload and a signature which are all encoded. We won’t get into full detail about the structure and inner workings of JWTs in this tutorial, but Chris covers it in The Anatomy of a JSON Web Token.

To fully understand how JWTs are used, we have to shift our thinking a bit. Traditional authentication requires that the server store the user’s authentication information which is checked every time the user makes a request. This method creates challenges when the application grows and needs to scale up, especially if it is distributed across several different servers. It also becomes problematic when we want to use our API for other purposes, such as for mobile applications. To get a better understanding of the limitations of server-based authentication and how JWTs can help, read The Ins and Outs of Token Based Authentication.

What We’ll Build

This tutorial will demonstrate how to implement token-based authentication in an AngularJS and Laravel application. To do so, we’ll build a simple app that will authenticate users with a login form. If successfully authenticated, the user will be redirected to a view where they can get a list of all users in the database. The focus of the tutorial will be on how we can generate JWTs on the Laravel side, obtain them on the front-end and then send them along with every request to the API.

angular-laravel-auth-9

We’ll be using a couple open source packages for this application: jwt-auth for creating JWTs on the Laravel side and Satellizer for handling the AngularJS authentication logic.

Installing the Laravel Dependencies

Let’s create a new Laravel application called jot-bot. Assuming you have Composer and the Laravel installer setup and ready to go, from the command line:

laravel new jot-bot

If everything worked correctly you should have all the Laravel files installed. The next step is to rename .env.example to .env so that Laravel can properly pull environment variables for the app.

It’s possible that the application key doesn’t properly generate for you on installation. If that is the case, you can generate a new key:

php artisan key:generate

APP_KEY within the .env file will need to be set to this new key. You can also take this opportunity to create a new database for the application and set the database credentials in the .env file. My .env file looks like this:

APP_ENV=local
APP_DEBUG=true
APP_KEY=lk7IqejFTEqaIep8guBE16Mg5JWpZtHj
    
DB_HOST=localhost
DB_DATABASE=jot-bot
DB_USERNAME=root
DB_PASSWORD=root

Next, let’s fire up the app to make sure everything is working:

cd jot-bot
php artisan serve

If everything is working you should see the Laravel welcome page.

angular-laravel-auth-1

Now that the core Laravel files are installed, let’s install jwt-auth. Open composer.json and update the require object to include jwt-auth:

// composer.json
    
...

"require": {
       "php": ">=5.5.9",
       "laravel/framework": "5.1.*",
       "tymon/jwt-auth": "0.5.*"
   },
   

Next, let’s bring this package in by running an update. From the command line:

composer update

We’ll now need to update the providers array in config/app.php with the jwt-auth provider. Open up config/app.php, find the providers array located on line 111 and add this to it:

Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class

We should also add in the jwt-auth facades which we can do in config/app.php. Find the aliases array and add these facades to it:

'JWTAuth'   => Tymon\JWTAuthFacades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuthFacades\JWTFactory::class

We also need to publish the assets for this package. From the command line:

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"

After you run this command you will see a new file in the config folder called jwt.php. This file contains settings for jwt-auth, one of which we need to change right away. We need to generate a secret key which we can do from the command line:

php artisan jwt:generate

You’ll see that after running this command we get a new value next to 'secret' where “changeme” was before.

We’ve got everything installed on the Laravel side—now let’s take care of the AngularJS dependencies.

Installing the AngularJS Dependencies

There are a number of things that need to happen on the front-end so that we can send a JWT with every request to the Laravel API after our user is authenticated. Namely, we need to keep the JWT in local storage once we retrieve it from the API and also need to add a header to every subsequent request that contains the token. We could write the appropriate JavaScript to accomplish this on our own, but a package has already been created that does a great job of it. Instead of spending extra effort, let’s make use of Satellizer.

Let’s use npm to install our front-end dependencies. From the command line:

cd public
npm install angular satellizer angular-ui-router bootstrap

Creating Some Test Data

Laravel comes with a migration for a users table out of the box and this is the only one we’ll need for the tutorial. Let’s run the migrations so that this table gets created in the database and then seed it with some test data. From the command line:

php artisan migrate

For seeding, we’ll put the array of users and the logic to insert them into the database right within DatabaseSeeder.php, but you can also create a separate seeder file and call it from that file if you like.

// database/seeds/DatabaseSeeder.php

<?php

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use App\User;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        Model::unguard();

        DB::table('users')->delete();

        $users = array(
                ['name' => 'Ryan Chenkie', 'email' => 'ryanchenkie@gmail.com', 'password' => Hash::make('secret')],
                ['name' => 'Chris Sevilleja', 'email' => 'chris@scotch.io', 'password' => Hash::make('secret')],
                ['name' => 'Holly Lloyd', 'email' => 'holly@scotch.io', 'password' => Hash::make('secret')],
                ['name' => 'Adnan Kukic', 'email' => 'adnan@scotch.io', 'password' => Hash::make('secret')],
        );
            
        // Loop through each user above and create the record for them in the database
        foreach ($users as $user)
        {
            User::create($user);
        }

        Model::reguard();
    }
}

In this seeder we are creating an array of users and then looping through them to add them to the database. This file relies on us using AppUser which is the User model that also ships with Laravel. As we loop through the users we call create on each to add that record to the database. With this in place, we just need to run the seeder.


php artisan db:seed

Creating the API Routes

Once we’ve confirmed that the database has been seeded properly, let’s get the API setup in routes.php.

// app/Http/routes.php

<?php

Route::get('/', function () {
    return view('index');
});

Route::group(['prefix' => 'api'], function()
{
    Route::resource('authenticate', 'AuthenticateController', ['only' => ['index']]);
    Route::post('authenticate', 'AuthenticateController@authenticate');
});

We’ve done a couple things here—first, we’ve changed the starting route to load a view that we’ll create later called index instead of welcome. Next, we’ve created a route group that is prefixed with api and that currently serves a resource called authenticate. We only really want the index method of this resource controller which we indicate with the third argument. We’ll also need a custom method called authenticate on this controller which handles generating and returning a JWT.

Now we need to create a resource controller called AuthenticateController. From the command line:

php artisan make:controller AuthenticateController

If that runs successfully you should now see AuthenticateController.php in app/Http/Controllers.

We’re going to need to use some pieces of the JWTAuth package in this controller.

// app/Http/controllers/AuthenticateController.php

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

use AppHttpRequests;
use AppHttpControllersController;
use JWTAuth;
use Tymon\JWTAuthExceptions\JWTException;


class AuthenticateController extends Controller
{

    public function index()
    {
        // TODO: show users
    }    
  
    public function authenticate(Request $request)
    {
        $credentials = $request->only('email', 'password');

        try {
            // verify the credentials and create a token for the user
            if (! $token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }
        } catch (JWTException $e) {
            // something went wrong
            return response()->json(['error' => 'could_not_create_token'], 500);
        }

        // if no errors are encountered we can return a JWT
        return response()->json(compact('token'));
    }
}

The try block in the authenticate method attempts to produce a token using the JWTAuth facade with the user’s credentials. If something goes wrong with that, the method will return a 401 and say the credentials are invalid. In other cases where an exception is thrown, it will return a 500 indicating an internal server error and saying that something went wrong. If we are able to get past that then we can return a token. Returning it with compact('token') puts the object on a key called token which will come in handy when we read it with Satellizer.

We’ll use this controller to show data for all users as well, but let’s first test out the API.

Testing Out the API

By default, Laravel has CSRF token verification turned on, but since we’re using JWTs in a stateless manner now, we don’t really need CSRF tokens. We can turn this default behavior off by commenting out the VerifyCsrfToken middleware in Kernel.php.

We’re also eventually going to need to use the middleware that jwt-auth provides. We can set that up in the routeMiddleware array in Kernel.php as well.

// app/Http/Kernel.php
    
...

namespace AppHttp;

use IlluminateFoundationHttpKernel as HttpKernel;

class Kernel extends HttpKernel
{

    protected $middleware = [
        Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        App\Http\Middleware\EncryptCookies::class,
        Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        Illuminate\Session\Middleware\StartSession::class,
        Illuminate\View\Middleware\ShareErrorsFromSession::class,
        // App\Http\Middleware\Verify\CsrfToken::class,
    ];

    protected $routeMiddleware = [
        'auth' => App\Http\Middleware\Authenticate::class,
        'auth.basic' => Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'guest' => App\Http\Middleware\RedirectIfAuthenticated::class,
        'jwt.auth' => Tymon\JWTAuth\MiddlewareGetUserFromToken::class,
        'jwt.refresh' => TymonJWTAuth\MiddlewareRefreshToken::class
    ];
}

Now that VerifyCsrfToken is turned off, let’s check the API with Postman.

If we send a POST request to localhost:8000/api/authenticate with the credentials for one of our users as URL parameters, we can see that we get a token returned.

angular-laravel-auth-3-1

Now that we’re successfully getting a token, let’s put it to use and setup our index method in the controller to return the data for all users if a token is present.

Showing User Data

We’re going to return the data for all of the users in the database, but only if there is a token passed along with the request. We can make this happen by protecting our API with the middleware that comes with jwt-auth.

Let’s add some logic to show all of the users if the token sent along with the request is valid.

// app/Http/Controllers/AuthenticateController.php

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use App\User;

class AuthenticateController extends Controller
{

public function __construct()
   {
       // Apply the jwt.auth middleware to all methods in this controller
       // except for the authenticate method. We don't want to prevent
       // the user from retrieving their token if they don't already have it
       $this->middleware('jwt.auth', ['except' => ['authenticate']]);
   }

public function index()
{
    // Retrieve all the users in the database and return them
    $users = User::all();
    return $users;
}

...

Here we are saying we want the jwt-auth middleware to be applied to everything in the controller except the authenticate method (we don’t want to block the user from retrieving their token) and we have the index method returning a list of all users.

If we try making a GET request to localhost:8000/api/authenticate without a JWT in as a header or URL parameter, we get a 400 error that says no token was provided.

angular-laravel-auth-4-1

If, however, we copy and paste the JWT we retrieved earlier as a URL parameter with the key of token, we get all the user data returned to us.

angular-laravel-auth-5-1

The jwt-auth middleware checks for the presence of the token and let’s the request through if it is there and is valid, but rejects the request if it is not.

Just to prove that the middleware is doing its job, let’s try removing a character from the token to invalidate it. We can see that the call we then make to the index method gets denied and we can’t see the users list.

angular-laravel-auth-6-1

Setting up The Front-End

Now that the API is setup and the middleware is functioning properly we can create the front-end of our app.

We’ll need to setup our initial view in an index.php file because this is what our Laravel routes.php file is setup to return when the user hits the main / route.

<!-- resources/views/index.php -->

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Angular-Laravel Authentication</title>
        <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css">
    </head>
    <body ng-app="authApp">

        <div class="container">
            <div ui-view></div>
        </div>        

    </body>

    <!-- Application Dependencies -->
    <script src="node_modules/angular/angular.js"></script>
    <script src="node_modules/angular-ui-router/build/angular-ui-router.js"></script>
    <script src="node_modules/satellizer/satellizer.js"></script>

    <!-- Application Scripts -->
    <script src="scripts/app.js"></script>
    <script src="scripts/authController.js"></script>
    <script src="scripts/userController.js"></script>
</html>

In the index.php file we have included all of the application dependency scripts that we installed earlier and have also put references in for the application scripts that we’ve yet to create. Since we’re using UI Router we are serving a ui-view in the middle of the page which is what will be used to handle our different states.

Next, let’s create our main app.js file.

// public/scripts/app.js

(function() {

    'use strict';

    angular
        .module('authApp', ['ui.router', 'satellizer'])
        .config(function($stateProvider, $urlRouterProvider, $authProvider) {

            // Satellizer configuration that specifies which API
            // route the JWT should be retrieved from
            $authProvider.loginUrl = '/api/authenticate';

            // Redirect to the auth state if any other states
            // are requested other than users
            $urlRouterProvider.otherwise('/auth');
            
            $stateProvider
                .state('auth', {
                    url: '/auth',
                    templateUrl: '../views/authView.html',
                    controller: 'AuthController as auth'
                })
                .state('users', {
                    url: '/users',
                    templateUrl: '../views/userView.html',
                    controller: 'UserController as user'
                });
        });
})();

Here we are loading the ui.router and satellizer modules and setting up some configuration for them. Satellizer gives us an $authProvider which can be used to configure its settings. In particular, we want to specify that when using Satellizer to login, the HTTP requests that get made to retrieve the JWT from the API should go to api/authenticate.

We also use $stateProvider to setup configuration for the two states that we’ll be using: auth and users.

We’ll now need to create views for the auth and users states and controllers to handle their behavior.

Setting Up the Auth State

// public/scripts/authController.js

(function() {

    'use strict';

    angular
        .module('authApp')
        .controller('AuthController', AuthController);


    function AuthController($auth, $state) {

        var vm = this;
            
        vm.login = function() {

            var credentials = {
                email: vm.email,
                password: vm.password
            }
            
            // Use Satellizer's $auth service to login
            $auth.login(credentials).then(function(data) {

                // If login is successful, redirect to the users state
                $state.go('users', {});
            });
        }

    }

})();

In our AuthController we are injecting $auth which is a service provided by Satellizer for communicating with the API and also $state so that we can handle redirects.

We’ve got one method in this controller—login—which is responsible for using the $auth service to make a call to the API to retrieve the user’s JWT. We setup our credentials object to contain an email address and password which we’ll get from the form fields in the view and then pass them to the login method on the $auth service. If the token is successfully retrieved we are redirected to the users state.

So what does the $auth service do exactly? If we dig into the Satellizer source we can see what’s happening when the login method is called on line 422.

// node_modules/satellizer/satellizer.js
...

local.login = function(user, redirect) {
    var loginUrl = config.baseUrl ? utils.joinUrl(config.baseUrl, config.loginUrl) : config.loginUrl;
    return $http.post(loginUrl, user).then(function(response) {
        shared.setToken(response, redirect);
        return response;
    });
...

We can see here that this method makes an $http.post call to the login URL that we specified in our config block in app.js and, if successful, sets the returned token in local storage.

Now let’s setup the template for the login page.

<!-- public/views/authView.html -->

<div class="col-sm-4 col-sm-offset-4">
    <div class="well">
        <h3>Login</h3>
        <form>
            <div class="form-group">
                <input type="email" class="form-control" placeholder="Email" ng-model="auth.email">
            </div>
            <div class="form-group">
                <input type="password" class="form-control" placeholder="Password" ng-model="auth.password">
            </div>
            <button class="btn btn-primary" ng-click="auth.login()">Submit</button>
        </form>
    </div>
</div>

In this view we setup two form fields—one for the user’s email address and the other for their password. Next we call the login method in our AuthController to submit the data.

We can now try logging in to see if we get our token set in local storage.

angular-laravel-auth-7

Password: secret

If everything worked out we should now see the token saved in local storage.

angular-laravel-auth-8

We will also have been redirected to the users state which is what we want; however, we don’t yet have a view or controller setup to handle this state. Let’s put that in now.

Setting Up the Users State

// public/scripts/userController.js

(function() {

    'use strict';

    angular
        .module('authApp')
        .controller('UserController', UserController);  

    function UserController($http) {

        var vm = this;
        
        vm.users;
        vm.error;

        vm.getUsers = function() {

            // This request will hit the index method in the AuthenticateController
            // on the Laravel side and will return the list of users
            $http.get('api/authenticate').success(function(users) {
                vm.users = users;
            }).error(function(error) {
                vm.error = error;
            });
        }
    }
    
})();

This controller has one method, getUsers, which makes an $http.get request to the API to fetch the data for all users. If the call is successful, the users data is placed on the vm.users key. If not, the error message that gets returned is placed on the vm.error key. Now let’s reflect this data in a view:

<!-- public/views/userView.html -->

<div class="col-sm-6 col-sm-offset-3">
    <div class="well">
        <h3>Users</h3>  
        <button class="btn btn-primary" style="margin-bottom: 10px" ng-click="user.getUsers()">Get Users!</button>
        <ul class="list-group" ng-if="user.users">
            <li class="list-group-item" ng-repeat="user in user.users">
                <h4>{{user.name}}</h4>
                <h5>{{user.email}}</h5>
            </li>
        </ul>
        <div class="alert alert-danger" ng-if="user.error">
            <strong>There was an error: </strong> {{user.error.error}}
            <br>Please go back and login again
        </div>
    </div>
</div>

When this state is first loaded there won’t be any data displayed because we have set it up so that the data is fetched when the Get Users! button is clicked. Since we have our token saved in local storage, we should be able to get a list of the users back when we click this button.

angular-laravel-auth-9

You might be wondering how we are successfully getting data back when we haven’t done anything to send the JWT along with our $http request. Satellizer is taking care of this for us behind the scenes and is including the token as a header. We can see this if we open up the network tab in developer tools and inspect the request that was just sent.

angular-laravel-auth-10

An Authorization header gets added to the request with a value of Bearer . The token from the header is parsed by the jwt-auth middleware on the backend and our request is granted if it is valid.

Note: You might need to modify your apache settings to allow for authorization headers to be sent. You can modify your .htaccess file with:
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

To prove that the request won’t be successful if the token isn’t present, let’s try deleting it from local storage. In developer tools, right-click the token and choose delete, then refresh the page.

angular-laravel-auth-11

As you can see, the error condition is hit in this case and we aren’t able to get the user data.

Wrapping Up

In this tutorial we have seen how we can authenticate our AngularJS and Laravel applications with JSON Web Tokens. We secured our API with jwt-auth and setup middleware so that the user data only gets returned if the token is present. We then used Satellizer to set the user’s token in local storage and to add it to the Authorization header of every subsequent request to the API.

There are a few other important things necessary for a full authentication setup that we didn’t look at in this tutorial, including:

  • Setting the logged-in user’s data (such as name and email address) and their authentication status in local storage or on $rootScope so that we can pass their information around from state to state
  • A way to redirect the user to the login state if they become logged out somehow (for example, if the token expires)
  • How to log the user out and the implications of token-based authentication on logout

To dive into these additional authentication aspects, head over to my site where we’ll continue Token-Based Authentication for AngularJS and Laravel Apps!

Drop Me a Line!

If you’d like to get more AngularJS and Laravel tutorials, feel free to head over to my website and signup for my mailing list. You should follow me on Twitter—I’d love to hear about what you’re working on!

Ryan Chenkie

Tech Writer at Auth0 where I create tutorials on the latest web technologies. I also write about Angular, Laravel and more on my site. Say hi to me Twitter!