This tutorial is out of date and no longer maintained.
Adding authentication to an AngularJS and Laravel application is not the most straightforward, 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 (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.
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.
We’ll be using a couple of open source packages for this application: jwt-auth for creating JWTs on the Laravel side and Satellizer for handling the AngularJS authentication logic.
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.
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\JWTAuthProviders\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.
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
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@example.com', 'password' => Hash::make('secret')],
['name' => 'Chris Sevilleja', 'email' => 'chris@example.com', 'password' => Hash::make('secret')],
['name' => 'Holly Lloyd', 'email' => 'holly@example.com', 'password' => Hash::make('secret')],
['name' => 'Adnan Kukic', 'email' => 'adnan@example.com', '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
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 of 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.
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.
Now that we’re successfully getting a token, let’s put it to use and set up our index
method in the controller to return the data for all users if a token is present.
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.
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.
The jwt-auth
middleware checks for the presence of the token and lets 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.
Now that the API is set up and the middleware is functioning properly we can create the front-end of our app.
We’ll need to set up our initial view in an index.php
file because this is what our Laravel routes.php
file is set up 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 configurations 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 log in, the HTTP requests that get made to retrieve the JWT from the API should go to api/authenticate
.
We also use $stateProvider
to set up the 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.
// 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 set up 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 set up 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 set up 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.
Password: secret
If everything worked out we should now see the token saved in local storage.
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 set up to handle this state. Let’s put that in now.
// 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.
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.
An Authorization
header gets added to the request with a value of Bearer <token>
. 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.
As you can see, the error condition is hit in this case and we aren’t able to get the user data.
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 set up 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:
$rootScope
so that we can pass their information around from state to stateTo dive into these additional authentication aspects, head over to my site where we’ll continue Token-Based Authentication for AngularJS and Laravel Apps!
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!
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!