API Auth for GraphQL in Laravel
I will demonstrate how you can get all client requests (even authentication) to go through your single GraphQL endpoint. This will allow you to only need one client package for handling communication to your API from your web app. I will migrate the Laravel Authentication scaffolding to GraphQL and will be using Passport to handle the API tokens. Then I will convert my VueJS frontend to use only GraphQL. I am utilizing Lighthouse GraphQL for the server-side graphql endpoint.
You can find the full code example here on Github. Be sure to use the graphql-demo branch.
I am building this demo off of another article. If you need the basics on GraphQL check it out!
I will be using Personal Access Tokens (PAC)for this demonstration. See the Laravel Docs for more on those. Once the user submits the credentials from the login page, we will see if they are valid. If so, we will create a PAC and return an encoded JWT. Passport will be able to decode this JWT and extract the PAC. I will then return a Http-Only cookie with this JWT and Laravel will encrypt it.
From the web app’s perspective, it doesn’t have to worry about any tokens because the cookie will get passed to Laravel on every request. I will have a Middleware that will handle this cookie and pass it over to Passport in a format that it understands. More on that later.
Let’s get started!
I will be utilizing a Lighthouse API Authentication package to handle most of the heavy lifting. This is a Multi-Tenant example so I will change a few things to work around that. Make sure you have Passport setup before moving on.
Add the auth package as a composer dependency.
composer require joselfonseca/lighthouse-graphql-passport-auth
Now publish the graphql schema and configuration file.
php artisan vendor:publish --provider="Joselfonseca\LighthouseGraphQLPassport\Providers\LighthouseGraphQLPassportServiceProvider"
This will create a schema file in
Now we need to modify the
AppServiceProvider.php to make sure that if a tenant is identified it will force the database connection to tenant. This will force passport to use our tenant databases versus the system.
This is my
AuthServiceProvider.php. This is a pretty standard Passport configuration and is just here for reference. Notice I am limiting the lifetime of the Personal Access Tokens. The default lifetime is one year. You can set this to whatever you need.
I created a custom middleware that will take the JWT from the cookie and set the Authorization header in the incoming request. I opted to do this so I didn’t have to create a custom authentication guard. I tried using Laravel’s CreateFreshApiTokens middleware, but that depends on having a persistent session which won’t work with our API. In only a few lines of code I replicated the CreateFreshApiTokens middleware and I didn’t have to mess with tokens on the client. How cool is that?
This will first check if there is an Authorization header and if not it will do a check if the _token is present. If it is present it will add the Authorization header to the request with the JWT and carry on. Note the the Cookie will be unencrypted at this point in the request.
Modifications to Kernel.php
AddQueuedCookies Middleware to the api group . This will allow us to Queue a cookie during a GraphQL resolver and it will attach it to the response. This is necessary because with GraphQL we won’t have control over the response object that is returned. The order matters!
In our GraphQL schema, we need a mechanism to protect queries and mutations. We can use the Middleware directive, but any exceptions that are thrown will just show a generic Internal Error message. I created a custom directive that mimics the regular
auth Middleware that Laravel provides.
Create a folder in app called
GraphQL and put a
Directives folder in it. Now create a file called
ProtectDirective.php and save the below contents.
There is a ton going on here! You don’t have to understand everything that is happening in this file. Just know that you can use
@protect(guards: [“api”]) in you schema file, and is the equivalent to using the
I like moving the root graphql folder to routes folder, but you can keep it there if you like. Just know that if you do move it you will need to update the
lighthouse-graphql-passport.php config file.
Here is my updated schema.
Notice the @protect directive on the root Query and Mutation types.
I modified the auth.graphql file to remove the refresh/access token mutations and protected the logout mutation. I also added a mutation for registering users.
We will need to create a custom login resolver since we aren’t using access/refresh tokens to authenticate.
Create the folder structure
Create a file called LoginResolver.php and put in the below contents
This will check if the user credentials are validate. I use the
Auth::once method so that no session data is stored. If the credentials are invalid I will throw an Exception which is returned as a GraphQL Error.
Next I will check to see if a _token cookie is already present and if not I will create one. The createToken method (From hasApiTokens trait) will create and save a Personal Access Token. However, it will return a JWT instead of the actual PAC. This is perfect, because the Authorization header needs a JWT and and authentication will fail if it is the straight up Access Token
To logout, we will revoke the Personal Access Token and delete the cookie. The next time the web app tries to authenticate it will get an authentication GraphQL error and redirect to the login page.
That is it for the server side! You can now remove the Auth Controllers and relevant web/api Routes from your application. Now we will change the Vue app to use the new Auth configuration
There isn’t too much we need to modify here. All of the heavy lifting for the authentication is handled in Laravel. We just have to move to using Vue Apollo instead of axios for our authentication. This just involves creating graphql client queries and updating the components to use Vue Apollo for auth.
Here is the Apollo configuration I am using. Nothing special here. I’m just redirecting to the login path on when the server throws an Unauthenticated Exception.
In the queries folder in your js root, create an auth.gql file. Put in the following content. We will import these queries into our Auth module. We can also use these directly in our components.
For this demonstration I used the Vue plugin system to create my auth module. You don’t have to do it like this, but I felt it made things cleaner.
Create this folder structure in your js root
plugins\vue-auth-graphql\. Now create an
index.js in there and past the following content.
This plugin is basically a wrapper around Apollo and makes the methods available using
Now we can import all of our modules! Here is my app.js file for reference.
There isn’t anything too crazy going on here. Just using the Auth module to login.
That is it! I moved all of the other Auth Components over as well. You can see those in the repo. I feel like this greatly simplifies the API authentication flow and provides the best of both worlds with usability and security.