Jul 30, 2016 | laravel, laravel 5.3, laravel passport, oauth

Introducing Laravel Passport

Series

This is a series of posts on New Features in Laravel 5.3.

!
Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

API authentication can be tricky. OAuth 2 is the reigning ruler of the various standards that you might consider, but it's complex and difficult to implement—even with the great packages available (League and Luca among them).

We're talking many routes, dozens of migrations, complicated configuration, and much more—even with amazing packages trying to simplify the situation as much as possible.

Laravel Passport is native OAuth 2 server for Laravel apps. Like Cashier and Scout, you'll bring it into your app with Composer. It uses the League OAuth2 Server package as a dependency but provides a simple, easy-to-learn and easy-to-implement syntax.

Laying the groundwork in Laravel 5.2

In Laravel 5.2, we got a new structure in our authentication system: multiple auth drivers. This means that, instead of there being a single auth system that is responsible for one app at a time, you can apply different auth systems to different routes (or in different environments). Out of the box, we got the same auth system we've always had and a new token-based auth system for APIs.

Laravel 5.2's token system was fine enough—but it wasn't really any more secure than normal password login. It was there, most importantly, to lay the groundwork for packages like Passport, which essentially adds a new "passport" driver you can use in your app to make certain routes OAuth2 authed.

Installing Passport

Follow these steps on any Laravel 5.3 app and you'll be on your way to the easiest OAuth 2 server possible:

  1. Install Passport via Composer.
    bash composer require laravel/passport
  2. Go to config/app.php, and add Laravel\Passport\PassportServiceProvider to your providers list.
  3. Run the new migrations; because of 5.3's multiple migrations paths, the new Passport migrations will be included in your normal migration path.
    bash php artisan migrate
  4. Run php artisan passport:install, which will create encryption keys (local files) and personal/password grant tokens (inserted into your database)
  5. Go to your User class and import the trait Laravel\Passport\HasApiTokens
  6. Add the OAuth2 routes: go to AuthServiceProvider and use Laravel\Passport\Passport, then in the boot() method run Passport::routes()

    // AuthServiceProvider
    public function boot()
    {
        $this->registerPolicies();
    
        Passport::routes();
    }
    
  7. [Optional] Define at least one scope in the boot() method of AuthServiceProvider, after Passport::routes() using Passport::tokensCan

    // AuthServiceProvider
    public function boot()
    {
        $this->registerPolicies();
    
        Passport::routes();
    
        Passport::tokensCan([
            'conference' => 'Access your conference information'
        ]);
    }
    
  8. In config/auth.php, guards.api.driver; change the api guard to use passport driver instead of token
    php // config/auth.php return [ ... 'guards' => [ ... 'api' => [ 'driver' => 'passport', // was previously 'token' 'provider' => 'users' ] ] ];
  9. Build the Laravel auth scaffold; we'll need our users to be able to log in before we can authenticate them with Passport bash php artisan make:auth
  10. Publish the Vue components, if you want to use them bash php artisan vendor:publish --tag=passport-components

This is a lot, but basically we're importing that package, registering it with Laravel, setting our User up to authenticate using it, adding a few routes for authentication and callbacks, and defining our first scope for users to have access via.

At this point, you're theoretically done. The server is installed and it works. That was fast! Your routes work, and you can create your clients and tokens either via Passport's Artisan commands or by building your own administrative tool on top of Passport's API. But before you make your decision, take a look at that API and the Vue components Passport provides out of the box.

Passport's management API

Passport exposes a JSON API for your frontend to consume to let you manage your clients and tokens.

Out of the box, Passport comes with Vue components that show how you might want to interact with this API in your app. You could use these components and call it done, or you could write your own tool to interact with the API.

Passport's default Vue frontend

Out of the box, Passport comes with three Vue components: passport-clients, which shows all of the clients you've registered; passport-authorized-clients, which shows all of the clients you've given access to your account; and passport-personal-access-tokens, which shows all of the "personal" tokens you've created for testing the API. We can register them in app.js:

Vue.component(
    'passport-clients',
    require('./components/passport/Clients.vue')
);

Vue.component(
    'passport-authorized-clients',
    require('./components/passport/AuthorizedClients.vue')
);

Vue.component(
    'passport-personal-access-tokens',
    require('./components/passport/PersonalAccessTokens.vue')
);

const app = new Vue({
    el: 'body'
});

And then use them in our HTML:

<!-- let people make clients -->
<passport-clients></passport-clients>

<!-- list of clients people have authorized to access our account -->
<passport-authorized-clients></passport-authorized-clients>

<!-- make it simple to generate a token right in the UI to play with -->
<passport-personal-access-tokens></passport-personal-access-tokens>

Let's walk through how they work and what they do.

We'll follow the example Taylor set in his talk at Laracon: We'll have a Passport-enabled server app at passport.dev and a consumer app at consumer.dev.

Here's what the admin panel (using the three components as shown above) will look on your Passport-enabled Laravel app:

Screenshot of Laravel Passport default components view

Let's create a new client:

Screenshot of Laravel Passport create client view

Once you create a client, the UI will return a "secret" and a "client ID". Go to your consuming client (another site or app; in this example, consumer.dev) and put that key and ID into your configuration for your OAuth2 Client. Here's what it looked like when I created a client for "Consumer.dev":

Screenshot of Laravel Passport clients list

Never worked with OAuth 2 before? In the particular type of authentication we're working with right now, the "authorization code" grant, the way that a client identifies themselves is with a "client ID" (like a primary key—sometimes just 1) and a "secret" (like a password or a token). Each "client" is something like a web site that connects to this web site's data, or a mobile client, or something else that relies on this app and needs to authenticate with it. Passport also enables "password" grant and "personal" grant.

To test our app, we're going to build a consumer app just like Taylor did in his keynote. Remember, with any OAuth 2 situation, we have at least two apps: first, our "server" app that is using Passport, which is the app that will be authenticating users, and the "consumer" app that will be requesting the authenticating. Imagine that Twitter is your "server" and a twitter client you're writing is your "consumer"; the Twitter client wants their user to be able to authenticate with Twitter so the client can display their tweets.

Here's the routes file for our consumer.dev client app, based on Taylor's Laracon demo. Remember, this is the app that is CONSUMING the OAuth authentication services, not the one providing it.

// routes/web.php

use Illuminate\Http\Request;

// First route that user visits on consumer app
Route::get('/', function () {
    // Build the query parameter string to pass auth information to our request
    $query = http_build_query([
        'client_id' => 3,
        'redirect_uri' => 'http://consumer.dev/callback',
        'response_type' => 'code',
        'scope' => 'conference'
    ]);

    // Redirect the user to the OAuth authorization page
    return redirect('http://passport.dev/oauth/authorize?' . $query);
});

// Route that user is forwarded back to after approving on server
Route::get('callback', function (Request $request) {
    $http = new GuzzleHttp\Client;

    $response = $http->post('http://passport.dev/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => 3, // from admin panel above
            'client_secret' => 'yxOJrP0L9gqbXxoxoFl5I22IytFOpeCnUXD3aE0d', // from admin panel above
            'redirect_uri' => 'http://consumer.dev/callback',
            'code' => $request->code // Get code from the callback
        ]
    ]);

    // echo the access token; normally we would save this in the DB
    return json_decode((string) $response->getBody(), true)['access_token'];
});

When you visit http://consumer.dev/ it builds an OAuth request URL using your client ID and scope and providing a post-auth callback URL, and then it redirects you over to the Passport site (passport.dev) for you to accept or reject the auth request.

Screenshot of Laravel Passport auth approval view

When you authorize, Passport will then redirect you back to your provided callback URL—in this case, http://consumer.dev/callback—and you'll now have access to your token. As you can see in the example above, you can do whatever you want with it—in this case we'll just echo it out to grab and use in a test we'll cover in a minute.

Testing your token

Assuming you created a client for your consumer app and then got a token for your user, let's now test out using it.

First, set up a route in your Passport-enabled app that we can be sure requires that the user is authenticated. The simplest option will be to return the user, which is actually already set up in your routes/api.php file by default:

// routes/api.php
Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:api');

Next, open up your favorite REST client (Postman, Paw, or manually write a query in PHP or CURL) and make a request to that route we set up: http://passport.dev/api/user. So that you get a useful response, be sure to set your Content-Type header to application/json and your Accept header to application/json.

With no authentication, you'll receive a 401 response showing you're not authenticated:

{
  "error": "Unauthenticated."
}

Now, remember how the callback route in our consumer app echoes the access token? Copy that token, and add a new header to your request named "Authorization". Set the value equal to "Bearer TOKENHERE", where TOKENHERE is your access token you copied from the callback route.

Now, you should see your actual user:

{
  "id": 1,
  "name": "Matt Stauffer",
  "email": "matt@mattstauffer.co",
  "created_at": "2016-09-08 10:45:00",
  "updated_at": "2016-09-08 10:45:00"
}

That's it! You have a fully functional OAuth 2 auth API!

There's a few more features, though. Let's take a look.

Personal tokens

Passport offers a helpful tool that's not built into other OAuth 2 packages: the ability for your users to easily create tokens for themselves to use to test out your app. Your power users (imagine one of your users who is a developer and wants to consider your API for building an app against) don't have to create an entire second consumer app and register it for use with the authorization code grant just to test your API; instead they can create "personal tokens" just for testing purposes on their own accounts.

To use personal tokens, create a "personal client" once (you don't have to do this if you've already run php artisan passport:install):

php artisan make passport:client --personal

Now you, and any of your users, can go to the Personal Access Tokens component and hit "Create New Token". At this point you're creating new tokens that have this single Personal Client listed as the client. You can delete these tokens just like you can revoke actual client tokens.

Scope middleware

If you're unfamiliar with the idea of scopes, they're things you can define so that a consumer can define which type of access they're requesting to your app. This allows things like "user" access vs "full" access, etc. Each scope has a name and a description, and then within the app you can define their impact.

We've already covered how to define a scope above. Now let's see the simplest way to define their impact: Scope middleware.

There are two middleware that you can add to your app. You can give them any shortcut you want, but for now we'll call them "anyScope" and "allScopes".

Let's go to app/Http/Kernel.php and add them to the $routeMiddleware property:

// App\Http\Kernel
...
protected $routeMiddleware = [
    ...
    // you can name these whatever you want
    'anyScope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
    'allScopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
];

Each of these middleware require you to pass one or more scope names to them. If you pass one or more scopes to "anyScopes", the user will have access to that route if they have granted access with any of the provided scopes. If you pass one or more scopes to "allScopes", the user will have access to that route if they have granted access to all of the provided scopes.

So, for example, if you want to limit users' access to routes based on whether they have the conference scope:

Route::get('/whatever', function () {
    // do stuff
})->middleware('anyScope:conference');

// Any of the given scopes
Route::get('/whatever', function () {
    // do stuff
})->middleware('anyScope:conference,otherScope');

// All of the given scopes
Route::get('/whatever', function () {
    // do stuff
})->middleware('allScopes:conference,otherScope');

Super-powered access to the API for frontend views

If you have a frontend that's consuming this API, in the same app, you may not want to do the whole OAuth dance in that app. But you might still want the OAuth flow to still be available for external API consumers.

Passport offers a trick for your frontend—which has your user already authenticated via Laravel and sessions—to access your API and get around the OAuth flow.

Go to app/Http/Kernel.php and add this new middleware to web:

Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,

This adds a JWT token as a cookie to anyone who's logged in using Laravel's traditional auth. Using the Synchronizer token pattern, Passport embeds a CSRF token into this cookie-held JWT token. Passport-auth'ed routes will first check for a traditional API token; if it doesn't exist, they'll secondarily check for one of these cookies. If this cookie exists, it will check for this embedded CSRF token to verify it.

So, in order to make all of your JavaScript requests authenticate to your Passport-powered API using this cookie, you'll need to add a request header to each AJAX request: set the header X-CSRF-TOKEN equal to the CSRF token for that page.

If you're using Laravel's scaffold, that'll be available as Laravel.csrfToken; if not, you can echo that value using the csrf_token() helper.

I know this seems a bit complex, but here's the basics: If you want your local app (maybe a Vue or React SPA) to access your API, but don't feel like programming a whole complex OAuth flow into it, and you want to have OAuth available to external users, Passport makes this incredibly simple. Powerfully simple. For more information and an example of how to set this up in Vue, check out the docs.

Conclusions

I've programmed a lot of OAuth servers. It's a pain. I don't love it at all.

Passport is one of my favorite new features in Laravel in years. Not only does it simplify things I've always hated doing, it also adds a load of new features that I've never even thought to add to my apps. I love it, and I can't wait to use it.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  laravel 5.3  •  laravel passport  •  oauth


This is part of a series of posts on New Features in Laravel 5.3:

  1. Jun 16, 2016 | laravel, laravel 5.3, echo, websockets
  2. Jun 27, 2016 | laravel, laravel 5.3
  3. Jun 29, 2016 | laravel, laravel 5.3, eloquent
  4. Jul 6, 2016 | laravel, laravel 5.3
  5. Jul 8, 2016 | laravel, laravel 5.3, eloquent
  6. Jul 25, 2016 | laravel, laravel 5.3
  7. Jul 26, 2016 | laravel, laravel 5.3
  8. Jul 27, 2016 | laravel, laravel 5.3, routing
  9. Jul 27, 2016 | laravel, laravel 5.3, laravel scout, laravel passport, mailable
  10. Jul 29, 2016 | laravel, laravel 5.3, laravel scout
  11. Jul 30, 2016 | laravel, laravel 5.3, laravel passport, oauth
  12. Aug 5, 2016 | laravel, laravel 5.3, mail, laravel mailables
  13. Aug 8, 2016 | laravel, laravel 5.3
  14. Oct 19, 2016 | laravel, laravel 5.3, laravel notifications, notifications
  15. Dec 21, 2016 | laravel, laravel 5.3, vuejs, vueify, authorization
  16. Dec 21, 2016 | laravel, laravel 5.3, queues
  17. Jan 30, 2017 | laravel, laravel 5.3, artisan

Subscribe

For quick links to fresh content, and for more thoughts that don't make it to the blog.