|
1 | 1 | <p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400"></a></p> |
2 | 2 |
|
3 | | -## Authentication Using Laravel Sanctum & Fortify for an SPA |
4 | | - |
5 | | -Previously I wrote about using Laravel Sanctum to build an API for a Vue SPA to consume. [The article](/articles/vuejs-auth-using-laravel-sanctum), was a very basic intro using API tokens and local storage to maintain authentication state. While there’s nothing wrong with that method for testing out an idea, the preferred and more secure method is to use cookies and sessions. In this article we will dive into using Sanctum with Fortify in a Laravel API, consumed by a separate Vue SPA. |
6 | | - |
7 | | -The project files for this article can be found on Github: |
8 | | - |
9 | | -- [Larvel API](https://github.com/garethredfern/laravel-api) |
10 | | -- [VueJS SPA](https://github.com/garethredfern/laravel-vue) |
11 | | - |
12 | | -### Laravel & Package Install |
13 | | - |
14 | | -First, set up the Laravel API as you normally would. My preferred option is to use Laravel [Sail](https://laravel.com/docs/8.x/sail), which I have written about [here](/articles/switching-to-laravel-sail). If you choose to run Laravel via Sail, your API will be accessible via http://localhost. |
15 | | - |
16 | | -Next install [Sanctum](https://laravel.com/docs/8.x/sanctum#installation) & [Fortify](https://laravel.com/docs/8.x/fortify). |
17 | | - |
18 | | -```bash |
19 | | -sail composer require laravel/sanctum |
20 | | - |
21 | | -sail artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider" |
22 | | -``` |
23 | | - |
24 | | -```bash |
25 | | -sail composer require laravel/fortify |
26 | | - |
27 | | -sail artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider" |
28 | | -``` |
29 | | - |
30 | | -> Note: I am using the sail command to enable artisan commands to run within the Docker container. |
31 | | -
|
32 | | -Next add Sanctum's middleware to your api middleware group within your application's app/Http/Kernel.php file: |
33 | | - |
34 | | -```php |
35 | | -'api' => [ |
36 | | - \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, |
37 | | - 'throttle:api', |
38 | | - \Illuminate\Routing\Middleware\SubstituteBindings::class, |
39 | | -], |
40 | | -``` |
41 | | - |
42 | | -Ensure the FortifyServiceProvider class is registered within the providers array of your application's config/app.php file. |
43 | | - |
44 | | -```php |
45 | | -/* |
46 | | - * Application Service Providers... |
47 | | - */ |
48 | | - |
49 | | -App\Providers\FortifyServiceProvider::class, |
50 | | -``` |
51 | | - |
52 | | -Set up a seed for adding a test user, in the DatabaseSeeder.php file add the following: |
53 | | - |
54 | | -```php |
55 | | -\App\Models\User::factory(1)->create( |
56 | | - [ |
57 | | - 'name' => 'Luke Skywalker', |
58 | | - |
59 | | - 'email_verified_at' => null, |
60 | | - ] |
61 | | -); |
62 | | -``` |
63 | | - |
64 | | -Run the migrations. If you get an error using Sail, checkout my notes [here](/articles/switching-to-laravel-sail#a-couple-of-gotchas): |
65 | | - |
66 | | -```bash |
67 | | -sail artisan migrate --seed |
68 | | -``` |
69 | | - |
70 | | -Don’t forget to add a sender address in the `.env` so that an email can be sent. |
71 | | - |
72 | | -```bash |
73 | | - |
74 | | -``` |
75 | | - |
76 | | -### Setting Up Sanctum |
77 | | - |
78 | | -Sanctum needs some specific set up to enable it to work with a separate SPA. First lets add the following in your .env file: |
79 | | - |
80 | | -```bash |
81 | | -SANCTUM_STATEFUL_DOMAINS=localhost:8080 |
82 | | -SPA_URL=http://localhost:8080 |
83 | | -SESSION_DOMAIN=localhost |
84 | | -``` |
85 | | - |
86 | | -The stateful domain tells Sanctum which domain you are using for the SPA. You can find the full notes and config for this in the config/sanctum.php file. As we are using cookies and sessions for authentication you need to add a session domain. This determines which domain the cookie is available to in your application. Full notes can be found in the config/session.php file and the [official documentation](https://laravel.com/docs/8.x/sanctum#spa-authentication). |
87 | | - |
88 | | -Add the following to `app/Http/Kernel` |
89 | | - |
90 | | -```php |
91 | | -use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; |
92 | | - |
93 | | -'api' => [ |
94 | | - EnsureFrontendRequestsAreStateful::class, |
95 | | - 'throttle:api', |
96 | | - \Illuminate\Routing\Middleware\SubstituteBindings::class, |
97 | | -], |
98 | | -``` |
99 | | - |
100 | | -### Setting Up CORS |
101 | | - |
102 | | -If you don’t get CORS set up correctly, it can be the cause (pardon the pun) of great frustration. The first thing to remember is that your SPA and API need to be running on the same top-level domain. However, they may be placed on different subdomains. Running locally (using Sail) the API will run on http://localhost and the SPA using the Vue CLI will normally run on http://localhost:8080 (the port may vary but that is OK). |
103 | | - |
104 | | -With this in place we just need to add the routes which will be allowed via CORS. Most of the API endpoints will be via `api/*` but Fortify has a number of endpoints you need to add along with the fetching of `'sanctum/csrf-cookie'` add the following in your config/cors.php file: |
105 | | - |
106 | | -```php |
107 | | -'paths' => [ |
108 | | - 'api/*', |
109 | | - 'login', |
110 | | - 'logout', |
111 | | - 'register', |
112 | | - 'user/password', |
113 | | - 'forgot-password', |
114 | | - 'reset-password', |
115 | | - 'sanctum/csrf-cookie', |
116 | | - 'email/verification-notification', |
117 | | -], |
118 | | -``` |
119 | | - |
120 | | -While you are in the config/cors.php file set the following: |
121 | | - |
122 | | -```php |
123 | | -'supports_credentials' => true, |
124 | | -``` |
125 | | - |
126 | | -The above ensures you have the `Access-Control-Allow-Credentials` header with a value of `True` set. You can read more about this in the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials). We will be passing this header via the SPA but more on that when we move to set it up. |
127 | | - |
128 | | -### Setting Up Fortify |
129 | | - |
130 | | -Fortify also has a config file (config/fortify.php) which will need some changes. First set the `home` variable to point at the SPA URL, this can be done via the .env variable. This is where the API redirects to during authentication or password reset when the operations are successful and the user is authenticated. |
131 | | - |
132 | | -```php |
133 | | -'home' => env('SPA_URL') . '/dashboard', |
134 | | -``` |
135 | | - |
136 | | -Next switch off using any Laravel views for the authentication features, the SPA is handling all of this. |
137 | | - |
138 | | -```php |
139 | | -'views' => false, |
140 | | -``` |
141 | | - |
142 | | -Finally, turn on the authentication features you would like to use: |
143 | | - |
144 | | -```php |
145 | | -'features' => [ |
146 | | - Features::registration(), |
147 | | - Features::resetPasswords(), |
148 | | - Features::emailVerification(), |
149 | | - Features::updateProfileInformation(), |
150 | | - Features::updatePasswords(), |
151 | | -], |
152 | | -``` |
153 | | - |
154 | | -### Redirecting If Authenticated |
155 | | - |
156 | | -Laravel provides a `RedirectIfAuthenticated` middleware which out of the box will try and redirect you to the home view if you are already authenticated. For the SPA to work you can add the following which will simply send back a 200 success message in a JSON response. We will then handle redirecting to the home page of the SPA using VueJS routing. |
157 | | - |
158 | | -```php |
159 | | -foreach ($guards as $guard) { |
160 | | - if (Auth::guard($guard)->check()) { |
161 | | - if ($request->expectsJson()) { |
162 | | - return response()->json(['error' => 'Already authenticated.'], 200); |
163 | | - } |
164 | | - return redirect(RouteServiceProvider::HOME); |
165 | | - } |
166 | | -} |
167 | | -``` |
168 | | - |
169 | | -### Email Verification |
170 | | - |
171 | | -Laravel can handle email verification as it normally would but with one small adjustment to the `Authenticate` middleware. First. Let’s make sure your `App\Models\User` implements the `MustVerifyEmail` contract: |
172 | | - |
173 | | -```php |
174 | | -class User extends Authenticatable implements MustVerifyEmail |
175 | | -{ |
176 | | - use Notifiable; |
177 | | - |
178 | | - // ... |
179 | | -} |
180 | | -``` |
181 | | - |
182 | | -In the `Authenticate` Middleware change the `redirectTo` method to redirect to the SPA URL rather than a Laravel view: |
183 | | - |
184 | | -```php |
185 | | -protected function redirectTo($request) |
186 | | -{ |
187 | | - if (! $request->expectsJson()) { |
188 | | - return url(env('SPA_URL') . '/login'); |
189 | | - } |
190 | | -} |
191 | | -``` |
192 | | - |
193 | | -With this is in place Laravel will now send out the verification email and when a user clicks on the verification link it will do the necessary security checks and redirect back to your SPA’s URL. |
194 | | - |
195 | | -### Reset Password |
196 | | - |
197 | | -Setting up the reset password functionality in the API is as simple as following the [official docs](https://laravel.com/docs/8.x/passwords#reset-link-customization). For reference here is what you need to do. |
198 | | - |
199 | | -Add the following at the top of `App\Providers\AuthServiceProvider` |
200 | | - |
201 | | -```php |
202 | | -use Illuminate\Auth\Notifications\ResetPassword; |
203 | | -``` |
204 | | - |
205 | | -Add the following in the `AuthServiceProvider` boot method, this will create the URL which is used in the SPA with a generated token: |
206 | | - |
207 | | -```php |
208 | | -ResetPassword::createUrlUsing(function ($user, string $token) { |
209 | | - return env('SPA_URL') . '/reset-password?token=' . $token; |
210 | | -}); |
211 | | -``` |
212 | | - |
213 | | -To make this all work we will need to have a reset-password view in the SPA which handles the token and passes back the users new password. This will be explained fully in the creating of the SPA post which will follow, you can review the code on [Github](https://github.com/garethredfern/laravel-vue/blob/main/src/views/ResetPassword.vue). |
214 | | - |
215 | | -### API Routes |
216 | | - |
217 | | -Once you have all the authentication in place, any protected routes will need to use the `auth:sanctum` middleware guard. This will ensure that the user has been authenticated before they can view the requested data from the API. Here is a simple example of what those endpoints would look like. |
218 | | - |
219 | | -```php |
220 | | -use Illuminate\Http\Request; |
221 | | - |
222 | | -Route::middleware('auth:sanctum')->get('/users/{user}', function (Request $request) { |
223 | | - return $request->user(); |
224 | | -}); |
225 | | -``` |
226 | | - |
227 | | -### Conclusion |
228 | | - |
229 | | -If you are wanting/needing to go down the route of having a completely separate SPA that consumes a Laravel API then hopefully this post has given you all the reference you need to get things set up for the API. In the next article we will focus on setting up the SPA. |
230 | | - |
231 | | -If you would like to hear an excellent explanation from Taylor on the how these packages came about I highly recommend listening to his [podcast episode](https://blog.laravel.com/laravel-snippet-25-ecosystem-discussion-auth-recap-passport-sanctum). |
232 | | - |
233 | | -The project files for this article can be found on Github: |
234 | | - |
235 | | -- [Larvel API](https://github.com/garethredfern/laravel-api) |
236 | | -- [VueJS SPA](https://github.com/garethredfern/laravel-vue) |
| 3 | +## Documentation |
237 | 4 |
|
| 5 | +The full documentation for this demo app can be found at [Build a Laravel Vue Spa](https://laravelvuespa.com/). |
0 commit comments