Laravel API: Authorization
This tutorial demonstrates how to add authorization to a Laravel API. We recommend you to Log in to follow this quickstart with examples configured for your account.
I want to integrate with my app
15 minutesI want to explore a sample app
2 minutesGet a sample configured with your account settings or check it out on Github.
This quickstart covers building an API protected by an Auth0-issued access token. This type of API is typically consumed by:
- Mobile, desktop, and other native applications using the Native login flow
- CLIs, daemons, or services running on your back-end using the Client Credentials Flow
If this API is only consumed by a web application on the same domain (in the case of AJAX actions or lazy loading content for an authenticated user) then the API protection should be handled by the application itself and the login flow secured by Auth0.
This example demonstrates:
-
How to check for a JSON Web Token (JWT) in the
Authorization
header of an incoming HTTP request. -
How to check if the token is valid, using the JSON Web Key Set (JWKS) for your Auth0 account. To learn more about validating Access Tokens, read the Validate an Access Token tutorial.
Configure Auth0 APIs
Create an API
In the APIs section of the Auth0 dashboard, click Create API. Provide a name and an identifier for your API, for example, https://quickstarts/api
. You will use the identifier as an audience
later, when you are configuring the Access Token verification. Leave the Signing Algorithm as RS256.
By default, your API uses RS256 as the algorithm for signing tokens. Since RS256 uses a private/public keypair, it verifies the tokens against the public key for your Auth0 account. The public key is in the JSON Web Key Set (JWKS) format, and can be accessed here.
Define Permissions
Permissions let you define how resources can be accessed on behalf of the user with a given access token. For example, you might choose to grant read access to the messages
resource if users have the manager access level, and a write access to that resource if they have the administrator access level.
You can define allowed permissions in the Permissions tab of the Auth0 Dashboard's APIs section.
Validate Access Tokens
Install dependencies
Protecting your Laravel API requires a middleware which will check for and verify a bearer token in the Authorization
header of an incoming HTTP request. We'll do that using tools provided by the Auth0 Laravel package.
$ composer require auth0/login
Configure the plugin
The laravel-auth0 plugin comes with a configuration file that can be generated using Artisan. First, generate the configuration file from the command line:
$ php artisan vendor:publish
Select the Auth0\Login\LoginServiceProvider
option. After the file is generated, it will be located at config/laravel-auth0.php
. Edit this file to add the configuration values needed to verify incoming tokens:
// config/laravel-auth0.php
return [
// ...
'authorized_issuers' => [ 'https://your-tenant.auth0.com/' ],
// ...
'api_identifier' => 'https://quickstarts/api',
// ...
'supported_algs' => [ 'RS256' ],
// ...
];
In more detail:
authorized_issuers
is an array of allowed token issuers. In this case, it would simply be an array with just your tenant URL.api_identifier
is the Identifier field of the API created above.supported_algs
is the Signing Algorithm field of the API created above. This value should be an array but only have a single value,RS256
.
Configure Apache
By default, Apache does not parse Authorization
headers from incoming HTTP requests. You may need to add the following to the .htaccess
file for your application:
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
Protect API Endpoints
The routes shown below are available for the following requests:
GET /api/public
: available for non-authenticated requestsGET /api/private
: available for authenticated requests containing an Access Token with no additional scopesGET /api/private-scoped
: available for authenticated requests containing an Access Token with theread:messages
scope granted
For the two private
API routes, we'll need a middleware to check for a bearer token in an Authorization
header for the request and then verify that the token is valid. We'll create that middleware using the make:middleware
Artisan command:
php artisan make:middleware CheckJWT
Now, let's implement the handle()
method that Laravel will call automatically for the route:
<?php
// app/Http/Middleware/CheckJWT.php
namespace App\Http\Middleware;
use Closure;
use Auth0\SDK\JWTVerifier;
class CheckJWT
{
/**
* Validate an incoming JWT access token.
*
* @param \Illuminate\Http\Request $request - Illuminate HTTP Request object.
* @param Closure $next - Function to call when middleware is complete.
*
* @return mixed
*/
public function handle($request, Closure $next) {
$accessToken = $request->bearerToken();
if (empty($accessToken)) {
return response()->json(['message' => 'Bearer token missing'], 401);
}
$laravelConfig = config('laravel-auth0');
$jwtConfig = [
'authorized_iss' => $laravelConfig['authorized_issuers'],
'valid_audiences' => [$laravelConfig['api_identifier']],
'supported_algs' => $laravelConfig['supported_algs'],
];
try {
$jwtVerifier = new JWTVerifier($jwtConfig);
$decodedToken = $jwtVerifier->verifyAndDecode($accessToken);
} catch (\Exception $e) {
return response()->json(['message' => $e->getMessage()], 401);
}
return $next($request);
}
}
This middleware:
- Checks that there is a Bearer token and stops the request if one was not found.
- Pulls in the configuration values needed to verify the token.
- Attempts to decode the token, catching any exceptions thrown if it is expired, malformed, or otherwise invalid.
Next, we register this middleware we in the HTTP Kernel with the name jwt
:
// app/Http/Kernel.php
// ...
class Kernel extends HttpKernel {
// ...
protected $routeMiddleware = [
// ...
'jwt' => \App\Http\Middleware\CheckJWT::class,
// ...
];
// ...
}
We are now able to protect individual API endpoints by applying the jwt
middleware:
// routes/api.php
// This endpoint does not need authentication.
Route::get('/public', function (Request $request) {
return response()->json(['message' => 'Hello from a public endpoint!']);
});
// These endpoints require a valid access token.
Route::middleware(['jwt'])->group(function () {
Route::get('/private', function (Request $request) {
return response()->json(['message' => 'Hello from a private endpoint!']);
});
});
The /api/private
route is now only accessible if a valid access token is included in the Authorization
header of the incoming request. We can test this by manually generating an access token for the API and using a tool like Postman to test the routes.
In the Auth0 Dashboard, go to the Test tab for the API created above and click the COPY TOKEN link.
Now, let's turn on the Laravel test server:
$ php artisan serve --port=3010
Send a GET
request to the public route - http://localhost:3010/api/public
- and you should receive back:
{ "message": "Hello from a public endpoint!" }
Now send a GET
request to the private route - http://localhost:3010/api/private
- and you should get a 401 status and the following message:
{ "message": "Bearer token missing" }
Add an Authorization
header set to Bearer API_TOKEN_HERE
using the token generated above. Send the GET
request to the private route again and you should see:
{ "message": "Hello from a private endpoint!" }
Configure the Scopes
The middleware we created above checks for the existence and validity of an access token but does not check the scope of the token. In this section, we will modify the middleware created above to check for specific scopes.
Then, in the existing CheckJWT
class, make the following changes:
// app/Http/Middleware/CheckJWT.php
// ...
class CheckJWT {
// Add the new parameter to this method.
public function handle ($request, Closure $next, $scopeRequired = null) {
// Existing code minus the return statement stays here ...
// ...
if ($scopeRequired && !$this->tokenHasScope($decodedToken, $scopeRequired)) {
return response()->json(['message' => 'Insufficient scope'], 403);
}
// Return statement is unchanged.
return $next($request);
}
/**
* Check if a token has a specific scope.
*
* @param \stdClass $token - JWT access token to check.
* @param string $scopeRequired - Scope to check for.
*
* @return bool
*/
protected function tokenHasScope($token, $scopeRequired) {
if (empty($token->scope)) {
return false;
}
$tokenScopes = explode(' ', $token->scope);
return in_array($scopeRequired, $tokenScopes);
}
}
In summary:
- We added a
$scopeRequired
parameter to thehandle()
method with a default value ofnull
. This will allow us to still handle private routes that do not need to check scope. - At the end of
handle()
, we check if the route requires a scope and, if so, that the token includes it. - We added a
tokenHasScope()
method to look for a specific scope within a decoded token.
Now, we can create a new middleware group that will check for both a valid token and a specific scope:
// routes/api.php
// ...
// These endpoints require a valid access token with a "read:messages" scope.
Route::middleware(['jwt:read:messages'])->group(function () {
Route::get('/private-scoped', function (Request $request) {
return response()->json(['message' => 'Hello from a private, scoped endpoint!']);
});
});
This route is now only accessible if an access token used in the request has a scope of read:messages
.
To test this route, first send a GET
request with no token to the private, scoped route - http://localhost:3010/api/private-scoped
- and you should get a 401 status and the following message:
{ "message": "Bearer token missing" }
Add an Authorization
header set to Bearer API_TOKEN_HERE
using the same token from the previous section. Send the GET
request to the private, scoped route again and you should get a 403 status and the following message:
{ "message": "Insufficient scope" }
Back in the Auth0 Dashboard:
- Go to the Machine to Machine Applications tab for the API created above.
- Scroll to the test Application for this API, make sure it's authorized, and click the down arrow icon.
- Add the
read:messages
scope and click Update (then Continue if needed).
- Click the Test tab, select the test Application, and click the COPY TOKEN link above the second code block.
Change the Authorization
header to use the new token and send the GET
request again. You should get a 200 status and the following message:
{ "message": "Hello from a private, scoped endpoint!" }
Obtaining Access Tokens
The example above uses manually-generated tokens which are not long-lived. Once your API is live on the web and ready to accept requests, the applications making the requests will need to get an access token for this API using one of a few ways:
- Web applications will use the Authorization Code Flow
- Mobile, desktop, and other native applications will use a Mobile/Native Login Flow
- CLIs, daemons, or services running on your back-end will use an Machine-to-Machine Flow
Regardless of the type, the application will need to request the audience of this API during the login flow to receive a correctly-formed access token.
Configure CORS (optional)
To configure CORS, you should add the laravel-cors
dependency. You can check it out here.
After installation, add HandleCors
middleware in the application's global middleware stack:
// app/Http/Kernel.php
protected $middleware = [
// ...
\Barryvdh\Cors\HandleCors::class,
];
Add the following to the configuration file for CORS
:
<?php
// config/cors.php
return [
'supportsCredentials' => true,
'allowedOrigins' => ['http://localhost:3000'],
'allowedOriginsPatterns' => [],
'allowedHeaders' => ['*'],
'allowedMethods' => ['*'],
'exposedHeaders' => [],
'maxAge' => 0,
];