-
Notifications
You must be signed in to change notification settings - Fork 0
Authentication
Hi, my name is Adarsh. Welcome to the last chapter in my tutorial on building a RESTful service using Laravel. Until now you've already learned a lot. You've learned what makes up a RESTful service, the theory, how to derive the structure of a RESTful service, how to then implement it using Laravel, parse requests, and send back responses, as well as in the last chapter, how to add database operations to the RESTful service, and use the database to store data, as well as get data and send it back to the user. Now, the last missing piece you will pretty much need in every RESTful service you may build is of course authentication.
That's the topic this chapter is about, and I want to give you a brief introduction on how authentication works in a RESTful service in general, and how you can implement it in your Laravel application. Therefore, in this section you will learn
-
Why session-powered authentication won't work in RESTful services. We'll then explore alternative approaches because, well, we need some way to do this, right?
-
And you will understand how to implement JSON Web Tokens as one possible way to add authentication to a RESTful service.
So let's start with the first point here.
- Session-powered authentication doesn't work.
If we have a look at traditional authentication, where we have our client, our browser for example, and we want to authenticate with a server, we send our login data to the server in this example, and the server will probably connect to a database and verify our information.
Provided that our information is valid, the server will then create a session, and therefore store the state of our validation, and from this point on our client is able to authenticate with the server through this session and cookies set on the client. This is a stateful authentication as the server is aware of our client, and we have that kind of connection.
"Traditional" Authentication
Server
-------
| | Database
1 | | 2 -----
Client ---> | | ----> | |
<--- |-------| <---- | |
cookies + 5 |Session| 3 -----
(set on client) | 4 |
STATEFUL AUTHENTICATION -------
Now, RESTful services, however, are stateless, which means we can't store a session.
RESTful Service Are Stateless!
Server
-------
| | Database
1 | | 2 -----
Client ---> | No | ----> | |
<--- |-------| <---- | |
cookies + 5 ~~Session~~ 3 -----
(set on client) | 4 |
-------
The RESTful service doesn't and shouldn't care which clients access it. So the server certainly doesn't store anything identifying our client, it shouldn't, as I said, and it won't. So, we can't use this authentication approach when using RESTful services.
Which alternatives do we have? Well one alternative would of course be to authenticate with each request.
----> ---->
Client Server Database
<---- <----
That means we send our authentication data to the server, the server looks it up in the database, and sends us back to required resource, and we then do this upon each request we send to the server, at least upon each request which accesses secured content. Well, everyone loves logging in with each request, isn't that right? Well it certainly isn't. That would be an awful service, and we probably wouldn't have that many users because imagine you have to log in each time you want to post something on Facebook. Well, you would probably post even less than you're currently doing.
So, this doesn't work, but a solution would be some kind of single sign on.
{user, password}
---->
<----
---- ---->
Client | | Server Database
| | <----
----
send with subsequent requests
---->
Token
So we send our data, user and password for example, to the server, the server checks this with the database, and then the server sends us something back, a document or some JSON data containing a token, and we can then send this token to the server with each subsequent request. Now of course this token has to be signed by the server, and it would contain the information about our client, but it isn't stored on the server, but the server is able to validate the token sent by the client, or all the clients accessing the server.
With that approach we can make sure that only valid clients, or authenticated clients are able to access our secured resources, while at the same time ensuring that our server isn't responsible for keeping that state.
For this validation approach there are two common concepts you'll encounter, JSON Web Tokens, also abbreviated with JWT, and OAuth2.
Now, sometimes you see a versus comparison of both topics, and that really isn't a comparison which makes sense because the two techniques here aren't alternatives.
JWT
JSON Web Token refers to an authentication protocol, it sets up an authentication protocol where we allow encoded claims to be transferred between two parties, a client and a server for example. The token is issued upon the identification of the clients, so when we send our data to the server, and the server checks if he's able to authenticate us with the database, and then the client stores the token, and sends it with each subsequent request.
OAuth2
On the other hand, OAuth2 is an authentication framework. This means it has a general set up where we have roles, client types and profiles, authentication grants, and endpoints to manage authentication.
It may very well issue JSON Web Tokens as an authentication mechanism in the end, but it's more than that, it's a whole framework as stated here.
And an error free implementation is a very complex task. Now you don't have to use OAuth2, you can just use JSON Web Tokens, and indeed I will do this in the project of this tutorial.
However, you may have a look at OAuth2, and if you are interested in diving deeper into all these authentication-related topics, and it certainly is an interesting topic, I stronger recommend checking out this tutorial on the Introduction to OAuth2, OpenID Connect and JSON Web Tokens by Dominick Baier on Pluralsight. There you will have the possibility to dive a lot deeper into this, as you can see here, this provides the content for a tutorial on its own. Therefore, in this tutorial I will stick to JSON Web Tokens, and not implement a full-scaled authentication framework like OAuth2.
Either way, if you're pointing this into production, make sure to use SSL because all your authentication, all your security mechanisms aren't worth that much if you're not encrypting data you're sending over the web. So that is a must anyways.
Now explain no matter if you use OAuth2 or not, JSON Web Tokens are a popular choice to implement this security mechanism. And therefore, it's a pretty good choice to implement in a Laravel application as well. And indeed, in the demo of this tutorial, I will show how to implement JSON Web Tokens in a Laravel application.
The steps necessary are to install a third-party package, because Laravel has the great advantage that there are a lot of third-party packages for a lot tasks you may encounter, and securing your RESTful service or implementing JSON Web Tokens is such a common task.
Next, you have to configure the package so that it fits your needs.
And then the step is to implement middleware to protect routes, which should be protected. Protecting routes is real easy with the Laravel package I'll show in this chapter, as it literally allows you to protect your routes, or your controller actions with one single line of code.
Finally, there may be points in your application where you want to retrieve the authenticated user. And that of course is possible with the JSON Web Token, since this token identifies a user and you're able to extract a user from the token. All that is handled by the package I'll show you to install, and it makes using JSON Web Tokens real easy.
So let's start with the first step, installing that third-party package. The package I'm using will be the jwt-auth package by Tymon tymon/jwt-auth, a real good JSON Web Token implementation for Laravel.
//Edit composer.json file
"require": {"tymon/jwt-auth": 0.5.*"}
The first step is to edit the composer.json file in your project and require this package. As of the time of this recording, the latest version is 0.5, and then .* to make sure to fetch the highest possible version there. But you may have a look at the appropriate GitHub page, to which you will find a link in the tutorial material, of course, to check out the latest version available.
Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class
The next step then is to go to the config.php file of your Laravel project, and add the provider for this package. This makes sure that the package you add into your application is actually available when you write code in your app.
// ... and Facade
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class
Also you should add the facade to have easy and quick access to the various methods exposed by that jwt-auth package.
Finally, you want to run php artisan vendor:publish
to make sure that the package's own config file gets copied into the app config files, or in the app config folder of your Laravel application to allow you a quick and easy access to it, and therefore allow you to configure the JSON Web Token package to your needs. So before I dive into configuring, let's start with the installation, and add this package to our app.
for JWT >= 1.0.0rc
Adarsh:laravel-rest adarshmaurya$ ../composer.phar update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 5 installs, 0 updates, 0 removals
- Installing symfony/polyfill-util (v1.10.0): Loading from cache
- Installing symfony/polyfill-php56 (v1.10.0): Loading from cache
- Installing namshi/jose (7.2.3): Loading from cache
- Installing lcobucci/jwt (3.2.5): Loading from cache
- Installing tymon/jwt-auth (1.0.0-rc.3): Loading from cache
namshi/jose suggests installing phpseclib/phpseclib (Allows to use Phpseclib as crypto engine, use version ^2.0.)
lcobucci/jwt suggests installing mdanter/ecc (Required to use Elliptic Curves based algorithms.)
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: beyondcode/laravel-dump-server
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Discovered Package: tymon/jwt-auth
Package manifest generated successfully.
Adarsh:laravel-rest adarshmaurya$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
Copied File [/vendor/tymon/jwt-auth/config/config.php] To [/config/jwt.php]
Publishing complete.
Adarsh:laravel-rest adarshmaurya$
So I'm here inside my project folder, opened in PhpStorm, my IDE, and I will go to the composer.json file here, and in the require field here I'll add another requirement besides the laravel/framework and php, and that will be the jwt package I just talked about. So it's by Tymon, and the name will be jwt-auth, and as explained the latest version is 0.5.*. You may also check out the GitHub repository of the package to find the link to the Wiki with the detailed installation and configuration documentation, or what the documentation general. That is a side note, but as explained you'll find a link to this in the description. Once you added this line to the composer.json file, you need to actually install this. Therefore open up a terminal and navigate into your project folder. There you may run composer install to pull in the newly added package. As you can see it's now installing this jwt-auth package here for me. If composer install shouldn't work for any reasons, try composer update instead. So the installation just finished, and back in the project the next step is to go view the config folder, and then the app.php file. Here, I need to add a provider to the newly-installed package so that we can actually use it. So I just copied that into here, but you don't have to type that, you may just with the GitHub page I showed earlier, and there you will find what you have to insert. But beware that this is not 100% correct, you need to copy this code here, but don't copy the single quotation marks, and then instead add double colons, so :: at the end, and then class, like you see it here. The next step is to scroll down in this file, and also add a facade for quick access to the exposed methods. This is the facade we need to add, and again you'll find a link in the GitHub repository where you need to add the :: class at the end, or of course you may just type it here. So once all this is done, the next step is to head back to the terminal, and there still navigate into your project folder, you have to run php artisan vendor:publish. This will copy the config file off the third party package into the Laravel app config folder. So if you have a look at your config folder, you'll see this new jwt.php file, and this is the file where you can configure your JSON Web Token package in this case.
Speaking of configuration, how is this done? Well, one important thing is to run php artisan jwt:generate
at the beginning, this will generate a random key used to sign your tokens, and this is of course very important to make sure that the encryption is actually a good encryption and not a bad one.
// Generate random key to sign tokens
php artisan jwt:generate
// In config/jwt.php, configure token
// Example: Time to live
'ttl' => 60
// Example: Underlying User Model
'user' => 'App\User'
Also, in the config folder, and there in the jwt.php file, you can configure your token. For example, you can set the time it has to live, for example 60 minutes, or the underlying user model you want to use for authentication. Now in the demo project we're using the default Laravel user under App\User, so the default will be fine there, but if you created your own user model, certainly make sure to adjust this appropriately. In general, of course, this file is there for you to fine tune the JSON Web Token, and this package to your needs. So let's see this in action.
Adarsh:laravel-rest adarshmaurya$ php artisan jwt:secret
jwt-auth secret [kWFWSs9ZH9CcpxNpWwI1tjeU1NxQbVXV] set successfully.
The first thing I'll do, again is a terminal navigated into the project folder, I will run php artisan jwt:secret
to generate this random key to sign my tokens, and then, in the project folder, I will go into this jwt file in the config folder, and here you can see the key which was just generated, and then you'll see all the other settings. For example, the time to live default is 1 hour, 60 minutes, and so on. Now for me all the defaults here are fine, but you may play around with that and configure this package to your needs. Again, consult the official GitHub page of this package for more information on the configuration and so on.
Great, so now everything has been installed and configured, but it's finally time to actually use it, right? So how can we sign in with JSON Web Tokens?
try {
if(! $token = JWTAuth::attempt($credentials)){
return response()->json(['error' => '...'], 401);
}catch(JWTException $e){
return response()->json(['error' => '...'], 500);
}
}
A common way would be to use a try catch block to see if we can create a token using the package we just installed with the credentials provided by the user, and if we fail here we may just return either the you're not authenticated message if we failed due to wrong credentials, or in case of any other exception we may just send back a generic error here. Now don't just take signing in with authenticating for resources. Signing in of course is the first step because the user has to be signed in to get a token, and we won't send back the token we generate here in the sign in process to the client. Authenticating with that token in the subsequent requests is something else, and I'll have a look at this later. For now, let's see this in action, and actually implement the signin method in our demo.
https://jwt-auth.readthedocs.io/en/docs/quick-start/#update-your-user-model So let's start with making few updates in the Users.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function meetings(){
return $this->belongsToMany('App\Meeting');
}
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
// TODO: Implement getJWTIdentifier() method.
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
// TODO: Implement getJWTCustomClaims() method.
return [];
}
}
JWTSubject
is implemennted in the User class. We have to override the function getJWTCustomClaims()
to return an empty array now.
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Exceptions\JWTException;
use JWTAuth;
class AuthController extends Controller
{
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request){
$this->validate($request, [
'name' => 'required',
'email' => 'required|email',
'password' =>'required|min:5'
]);
$name = $request->input('name');
$email = $request->input('email');
$password = $request->input('password');
$user = new User([
'name' => $name,
'email' => $email,
'password' => bcrypt($password)
]);
if($user->save()){
$user->signin = [
'href' => 'api/v1/user/signin',
'method' => 'POST',
'params' => 'email, password'
];
$response =[
'msg' => 'User created',
'user' => $user
];
return response()->json($response,201);
}
$response =[
'msg' => 'An error occurred',
];
return response()->json($response, 404);
}
/**
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @throws \Illuminate\Validation\ValidationException
*/
public function signin(Request $request){
//https://jwt-auth.readthedocs.io/en/docs/quick-start/#update-your-user-model
$this->validate($request, [
'email' => 'required|email',
'password' => 'required'
]);
$credentials = $request->only('email', 'password');
try{
if(! $token = JWTAuth::attempt($credentials)){
return response()->json(['msg' => 'Invalid credentials'], 401);
}
}catch(JWTException $e){
return response()->json(['msg' =>'Could not create token'], 500);
}
return response()->json(['token'=> $token]);
//return "It works";
}
}
So next in the project here in the AuthController
, in the signin action, we're currently not doing a whole lot, but that is finally about to change. Time to implement the try catch block you just saw in the slides. I am already extracting email and password, and what I'm doing now is I'll get rid of this user here, and I'll also get rid of this response for this action here, and instead I'll add the try catch block you just saw. So try, and then catch the JWTException e. Also make sure to add the import here to the JWTException namespace, it was added automatically in my IDE, but that is certainly something which is easy to miss, so make sure to use this namespace here. Now for logging in I'll replace these two variables here with a new one, credentials, where I will simply use the only method on my request to extract only, that's where the name comes from, two fields I expect to get or I know I will get, and then I can use these credentials in my login mechanism here. So inside this try block I'll add an if statement where I will check if the authentication is not successful, but I will try to create a token with the JWTAuth facade, and make sure to add import here at the top by typing use JWTAuth. And on this facade I can call the attempt method, and of course this name is no coincidence, in Laravel's default of helper or facade, you also go the attempt method to try to log in a user, and in the end this JSON Web Token package will fall back to Laravel's built-in authentication, or at least if you set it up like this in the config file, which is the default. So to this attempt method I will just pass these credentials, array in the end it is, or this variable here, and this will try to create this token. If this fails however, I will return a response, type json of course, where I will simply output a message where I say, Invalid credentials, and send back an error message, or an error code of 401 unauthenticated. In case of any other error, this is here in the catch block, I'll also send back a response of type json, here I will also provide a message where I simply say, Could not create token, and specify an error code of 500. In the case that we are successful, and this could happen too, we're very optimistic here until now, so in the case if we are successful, I'll send back an array where I assign the token to a token field here.
So, let's try this out in Postman. I already created a user with the password test_pw, and now we'll try to sign this user in.
POST http://192.168.0.4:8000/api/v1/user/signin
{
"email" : "[email protected]",
"password" : "test_pw"
}
As you can see we get back a response with the token, and then this very cryptic string, which looks about right, this is how you would imagine an encrypted token to look like.
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xOTIuMTY4LjAuNDo4MDAwXC9hcGlcL3YxXC91c2VyXC9zaWduaW4iLCJpYXQiOjE1NDM5NTg3MDcsImV4cCI6MTU0Mzk2MjMwNywibmJmIjoxNTQzOTU4NzA3LCJqdGkiOiJzYmkxeE9HVkZVNkYzVjlUIiwic3ViIjpudWxsLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.4qVlti31TSEjM4vIVCMjqqrv-p9HzTOxrA6jbCYMTcY"
}
So, the sign in process is working, and this is the token we'll need for future requests to send with the request to tell our application, or the JSON Web Token package, that we want to authenticate ourselves with that token. So let's have a look at how to do this next.
So that's the last missing piece in this whole authentication process using the token we're sending with requests, now how complicated could that be?
It's very easy, to be honest. In the controller constructor you may just add middleware, like this,
$this->middleware('jwt.auth');
and then we use the jwt.auth middleware provided by the package installed earlier to make sure that this controller, or if you add the only configuration for example as I will show in the demo, only the protected routes or actions in this controller with that middleware.
$user = JWTAuth::parseToken()->authenticate()
Additionally, also an important method is the parseToken method on the JWTAuth facade, which allows us to extract the authenticated user. That user, of course, is the core encoded into that token, and you may need to use that in your application to connect it with, for example, newly created posts, or in case of our demo, meetings, and so on. So let's see this in action too.
I'll start in the MeetingController where I want to protect certain routes with the middleware. So I'll comment out this middleware and add it here with jwt.auth, this is the middleware provided by the package, however, I don't want to protect all the actions in this controller as seeing all posts, or all meetings should be possible for example. Therefore, I'll add a second argument, which is an array where I then have my field, or the key name of only to make sure that I only want to protect the following routes, and of course you could also use except, but I like to be explicit here. And then I'll list all the routes I want to protect, or all the actions here. Now, which actions do I want to protect? I want to protect the store action, the update action, and the destroy action.
public function __construct()
{
$this->middleware('jwt.auth', ['only' =>[
'update', 'store', 'destroy'
]]);
}
So I'll add this here, update, store, and destroy. And with that I make sure the middleware only gets applied to these routes. However, this will not completely work like this out of the box. Yes, the package ships with that middleware, but this name here is set up by me, or by us.
So, we have to register this. We can do this in the Kernel.php file in the app Http folder. And here under routeMiddleware, you want to set up this middleware by adding it here, jwt.auth, and then assign a value of \Tymon\JWTAuth\Middleware\GetUserFromToken::class. This will make sure that this middleware the package ships with is associated with this name, jwt.auth, and of course you may change the name if you'd like another one, and then you can use it like this in the middleware up here.
I'll then copy this whole constructor here over to the RegistrationController
where I will need this too, however here I want to protect all routes so I can get rid of the only configuration here. The next step is back in the MeetingController
to extract the user where we need it.
<?php
namespace App\Http\Controllers;
use App\Meeting;
use Carbon\Carbon;
use Illuminate\Http\Request;
use JWTAuth;
class MeetingController extends Controller
{
public function __construct()
{
$this->middleware('jwt.auth', ['only' =>[
'update', 'store', 'destroy'
]]);
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$meetings = Meeting::all();
foreach($meetings as $meeting){
$meeting->view_meeting = [
'href' => 'api/v1/meeting/'. $meeting->id,
'method' =>'GET'
];
}
$response =[
'msg' => 'List of all Meetings',
'meetings' => [ $meetings
]
];
return response()->json($response, 200);
//return "It works";
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$this->validate($request,[
'title' => 'required',
'description' => 'required',
'time' => 'required|date_format:YmdHie',
]);
if(! $user = JWTAuth::parseToken()->authenticate()){
return response()->json(['msg' => 'User not found'], 404);
}
$title = $request->input('title');
$description = $request->input('description');
$time= $request->input('time');
//$user_id = $request->input('user_id');
$user_id = $user->id;
$meeting = new Meeting([
'time' => Carbon::createFromFormat('YmdHie', $time),
'title' => $title,
'description' => $description
]);
if($meeting->save()){
$meeting->users()->attach($user_id);
$meeting->view_meeting = [
'href' => 'api/v1/meeting' . $meeting->id,
'method' => 'GET'
];
$message =[
'msg' => 'Meeting created',
'meeting' => $meeting
];
return response()->json($message, 201);
}
//return "It works";
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$meeting = Meeting::with('users')->where('id',$id)->firstOrFail();
$meeting->view_meeting =[
'href' => 'api/v1/meeting',
'method'=>'GET'
];
$response =[
'msg' => 'Meeting information',
'meeting' => $meeting
];
return response()->json($response,200);
//return "It works";
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
* @throws \Illuminate\Validation\ValidationException
*/
public function update(Request $request, $id)
{
$this->validate($request, [
'title' => 'required',
'description' => 'required',
'time' => 'required|date_format:YmdHie',
]);
if(! $user = JWTAuth::parseToken()->authenticate()){
return response()->json(['msg' => 'User not found'], 404);
}
$title = $request->input('title');
$description = $request->input('description');
$time= $request->input('time');
//$user_id = $request->input('user_id');
$user_id = $user->id;
$meeting = Meeting::with('users')->findOrFail($id);
if(!$meeting->users()->where('users.id', $user_id)->first()){
return response()->json([
'msg' => 'user not registered for meeting, update not successful'
], 401);
};
$meeting->time = Carbon::createFromFormat('YmdHie', $time);
$meeting->title = $title;
$meeting->description = $description;
if(!$meeting->update()){
return response()->json([
'msg' => 'Error during updating'
], 401);
}
$meeting->view_meeting =[
'href' => 'api/v1/meeting/' . $meeting->id,
'method' => 'GET'
];
$response =[
'msg' => 'Meeting updated',
'meeting' => $meeting
];
return response()->json($response,200);
//return "It works";
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
$meeting = Meeting::findOrFail($id);
if(! $user = JWTAuth::parseToken()->authenticate()){
return response()->json(['msg' => 'User not found'], 404);
}
if(!$meeting->users()->where('users.id', $user->id)->first()){
return response()->json([
'msg' => 'user not registered for meeting, update not successful'
], 401);
};
$users = $meeting->users;
$meeting->users()->detach();
if(!$meeting->delete()){
foreach($users as $user){
$meeting->users()->attach($user);
}
return response()->json(['msg' => 'deletion failed'], 404);
}
$response =[
'msg' => 'Meeting deleted',
'create' => [
'href' => 'api/v1/meeting',
'method' => 'POST',
'params' => 'title, description, time'
]
];
return response()->json($response, 200);
//return "It works";
}
}
I'll first do this here when we create a new user in the store action, and I will get rid of the user_id validated here, I do no longer require it here. Instead what I will do is I'll add a new line here where I will simply create a simple if block where I check if I'm able to extract a user, or specifically if I fail to do that because I want to handle this case, otherwise it will have a user available, so I check if I can do this with the JWTAuth facade, which of course again I need to import here at the top by importing it like this. And then, I access the parseToken method, which will just check if the token's provided, I know that, otherwise you would not get here with the middleware applied to this action, but then I try to parse token, which I can of course do as the server is also signing the token, therefore I'm able to parse it as well, and then by calling the authenticate method on it I am able to extract a user authenticated with the token. So if that fails, notice the exclamation mark at the beginning, if that fails I want to return a response type json where I simply pass a message saying, User not found, and then 404 status code. Otherwise, I do have the user available, and now I may simply replace the access here to get the user from my, or the user_id not from the request, but from the user object extracted from the token. I will copy this user extraction code here, and continue to the update action. Here I also want to extract the user, so I will no longer need the user ID here too. Instead I will try to extract the user here, and then I replace this part here where I extract the user_id from the request with the extraction from the parsed user too, like I did before when creating a meeting. With that, all the other code here will still work since I'm only replacing the way I retrieve the user ID, and this has of course the advantage that now it's no longer possible to pass the ID of another user, even though you have to be authenticated, but you could still pass the ID of another user, but now that's no longer possible because now only the user ID of the user sending the request will be taken into account, and you're no longer free to send any user ID. Now with that I'll also copy this one more time, go down to the destroy method to also check this here after finding a meeting, and I'll also copy the code which checks if the user is actually signed up for this meeting. Here, of course, I will replace user_id with user id, so access the ID on the retrieved user. And, of course this user retrieved here is simply the user as it can be retrieved from the database, therefore I have access to all these fields. And if that is successful I want to detach the user, and so on, so this does not change, this is still the same, I just wanted to add these extra checks up here.
Now before I move on to the RegistrationController
, I think it's a great time to now test this all in Postman and see if this actually works the way we want it to work.
php artisan serve --host=192.168.0.4
POST http://192.168.0.4:8000/api/v1/user
{
"name" : "monk3",
"email" : "[email protected]",
"password" : "test_pw"
}
{
"msg": "User created",
"user": {
"name": "monk3",
"email": "[email protected]",
"updated_at": "2018-12-04 23:13:44",
"created_at": "2018-12-04 23:13:44",
"id": 10,
"signin": {
"href": "api/v1/user/signin",
"method": "POST",
"params": "email, password"
}
}
}
So I will create a new user for that, just give this an email of [email protected] , I'll keep the very secure password of test_pw. So signing up worked, and now I try to sign in with this user, and this works too.
POST http://192.168.0.4:8000/api/v1/user/signin
{
"email" : "[email protected]",
"password" : "test_pw"
}
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xOTIuMTY4LjAuNDo4MDAwXC9hcGlcL3YxXC91c2VyXC9zaWduaW4iLCJpYXQiOjE1NDM5NjUzMjAsImV4cCI6MTU0Mzk2ODkyMCwibmJmIjoxNTQzOTY1MzIwLCJqdGkiOiJqYUlzaWtRa29lSWNhZzNSIiwic3ViIjoxMCwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.RDtL0HnV0FtNANQbRIO2CYTclnpzI3J_HXByLi5K2EM"
}
I get back a token. Now, the next step is to copy that token here, and the question is how to do you then supply it for future requests? How does this third party package we installed actually expect to get this token? Well, there are two ways to do this.
You can either set this Authorization header here, where you specify Bearer:, then an empty space, and then the token you just got back, or my preferred way, you leave that header out, and instead you add ?token so you add it as a query parameter at the end of the URL.
Then I will create a new meeting, name it Monk Test Meeting, I don't need to add the user_id anymore, now we'll send this, and as you can see I get this Meeting created text.
php artisan serve --host=192.168.0.4
POST http://192.168.0.4:8000/api/v1/meeting?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xOTIuMTY4LjAuNDo4MDAwXC9hcGlcL3YxXC91c2VyXC9zaWduaW4iLCJpYXQiOjE1NDM5NjUzMjAsImV4cCI6MTU0Mzk2ODkyMCwibmJmIjoxNTQzOTY1MzIwLCJqdGkiOiJqYUlzaWtRa29lSWNhZzNSIiwic3ViIjoxMCwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.RDtL0HnV0FtNANQbRIO2CYTclnpzI3J_HXByLi5K2EM
{
"time" : "201801301330CET",
"title" : "Monk Test Meeting ",
"description" : "Monk Test"
}
{
"msg": "Meeting created",
"meeting": {
"time": {
"date": "2018-01-30 13:30:00.000000",
"timezone_type": 2,
"timezone": "CET"
},
"title": "Monk Test Meeting",
"description": "Monk Test",
"updated_at": "2018-12-04 23:18:42",
"created_at": "2018-12-04 23:18:42",
"id": 6,
"view_meeting": {
"href": "api/v1/meeting6",
"method": "GET"
}
}
}
Now what do I get if I remove one single character from the token? I get token invalid, which is what I would expect to get.
{
"message": "Could not decode token: Error while decoding to JSON: Syntax error",
"exception": "Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException",
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/tymon/jwt-auth/src/Http/Middleware/BaseMiddleware.php",
"line": 74,
"trace": [
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/tymon/jwt-auth/src/Http/Middleware/Authenticate.php",
"line": 30,
"function": "authenticate",
"class": "Tymon\\JWTAuth\\Http\\Middleware\\BaseMiddleware",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 151,
"function": "handle",
"class": "Tymon\\JWTAuth\\Http\\Middleware\\Authenticate",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 53,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php",
"line": 41,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 151,
"function": "handle",
"class": "Illuminate\\Routing\\Middleware\\SubstituteBindings",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 53,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php",
"line": 58,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 151,
"function": "handle",
"class": "Illuminate\\Routing\\Middleware\\ThrottleRequests",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 53,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 104,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 684,
"function": "then",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 659,
"function": "runRouteWithinStack",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 625,
"function": "runRoute",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 614,
"function": "dispatchToRoute",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"line": 176,
"function": "dispatch",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 30,
"function": "Illuminate\\Foundation\\Http\\{closure}",
"class": "Illuminate\\Foundation\\Http\\Kernel",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/fideloper/proxy/src/TrustProxies.php",
"line": 57,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 151,
"function": "handle",
"class": "Fideloper\\Proxy\\TrustProxies",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 53,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
"line": 31,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 151,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 53,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
"line": 31,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 151,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 53,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php",
"line": 27,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 151,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 53,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php",
"line": 62,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 151,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\CheckForMaintenanceMode",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"line": 53,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 104,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"line": 151,
"function": "then",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"line": 116,
"function": "sendRequestThroughRouter",
"class": "Illuminate\\Foundation\\Http\\Kernel",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/public/index.php",
"line": 55,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Kernel",
"type": "->"
},
{
"file": "/Users/adarshmaurya/Playground/play-by-play-php-laravel-restful-web-services-started/laravel-rest/server.php",
"line": 21,
"function": "require_once"
}
]
}
And if I try to update this meeting, let's say Monk Test Meeting id=6, which is the one I just created, and I also add the token here with the query parameter, this works.
php artisan serve --host=192.168.0.4
GET http://192.168.0.4:8000/api/v1/meeting/6?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xOTIuMTY4LjAuNDo4MDAwXC9hcGlcL3YxXC91c2VyXC9zaWduaW4iLCJpYXQiOjE1NDM5NjUzMjAsImV4cCI6MTU0Mzk2ODkyMCwibmJmIjoxNTQzOTY1MzIwLCJqdGkiOiJqYUlzaWtRa29lSWNhZzNSIiwic3ViIjoxMCwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.RDtL0HnV0FtNANQbRIO2CYTclnpzI3J_HXByLi5K2EM
{
"msg": "Meeting updated",
"meeting": {
"id": 6,
"created_at": "2018-12-04 23:18:42",
"updated_at": "2018-12-04 23:22:36",
"time": {
"date": "2018-12-04 01:46:00.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"title": "TitleX",
"description": "DescriptionY",
"view_meeting": {
"href": "api/v1/meeting/6",
"method": "GET"
},
"users": [
{
"id": 10,
"name": "monk3",
"email": "[email protected]",
"email_verified_at": null,
"created_at": "2018-12-04 23:13:44",
"updated_at": "2018-12-04 23:13:44",
"pivot": {
"meeting_id": 6,
"user_id": 10
}
}
]
}
}
But what happens if I sign in with another user? So, remember I just used [email protected], but I also got a user monk2, I created that before, so if I get the token for this user,
php artisan serve --host=192.168.0.4
GET http://192.168.0.4:8000/api/v1/user/signin
{
"email" : "[email protected]",
"password" : "test_pw"
}
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xOTIuMTY4LjAuNDo4MDAwXC9hcGlcL3YxXC91c2VyXC9zaWduaW4iLCJpYXQiOjE1NDM5NjU4NDQsImV4cCI6MTU0Mzk2OTQ0NCwibmJmIjoxNTQzOTY1ODQ0LCJqdGkiOiJQb21zeUpzRDFuMXlRYm9mIiwic3ViIjo5LCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.j3hglMpJFo0aFNpM7Am8xdCUyV85vDd1PccOIRMLh-g"
}
and now I try to update meeting 6 with just the one created by the other user, not the one I currently got a token of, so I if I use that token of the user here, let's see, I get user not registered for meeting.
{ "msg": "user not registered for meeting, update not successful" }
Great, so that all works, and with that it's only a tiny step left to actually configure and protect registration.
So back in the project I'll go to my RegistrationController
, and the middleware is already in place, so the routes are protected. However, what I want to do for deleting a registration is I want to fetch the currently-authenticated user and detach that. Remember, currently I'm detaching all users from a meeting upon deleting a registration, and that certainly is not the behavior I want. So, in order to only detach one user, or the user sending the request, I will for a second go back to the MeetingController, and grab the code to find out if, or to parse the user from the token, as well as the code checking if the user is actually registered. So I'll go back here, I'll override this code here, so now I'm still just extracting the user from the token sent with the request, and I check if the user is registered, otherwise the delete operation is not successful, of course, you can find more elaborate error messages. Also, for example, you may add a link here to send a link to the registration if you find that more appropriate. But, here I simply want to keep this compact and focus on the core things, which is of course extracting the user and detaching it from the meeting. So I'm extracting the user here, and if that is successful, and the user is also registered for a meeting, so at this point of time here, I can detach the user. I do this by using the meeting I already got from the very first line in this function here, or in this method here, then I use the relation to the users this meeting may have, and I again call detach like before, but this time I pass an argument to detach, I pass the user id of the user I want to detach. So now not all users are detached, but only the one with the fitting ID. With that, the user is unregistered from the meeting, I can also pass this user with the response because the user of course wasn't deleted, just the registration was deleted. Now of course for this to work make sure to add the import to use this JWTAuth facade in this controller too. And with that we should be good to go. So let's try this out too.
I will sign in again with my first user, with [email protected] to get a token, just copy that real quick.
php artisan serve --host=192.168.0.4
POST http://192.168.0.4:8000/api/v1/user/signin
{
"email" : "[email protected]",
"password" : "test_pw"
}
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xOTIuMTY4LjAuNDo4MDAwXC9hcGlcL3YxXC91c2VyXC9zaWduaW4iLCJpYXQiOjE1NDM5NjYzOTQsImV4cCI6MTU0Mzk2OTk5NCwibmJmIjoxNTQzOTY2Mzk0LCJqdGkiOiJra1JLeUkwTnFuSW1qSmFCIiwic3ViIjoxMCwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.WFm09Znjt7CvdwPvUurVwBDPk50TCjnYj0ZrMe3Hy44"
}
http://192.168.0.4:8000/api/v1/meeting/registration?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xOTIuMTY4LjAuNDo4MDAwXC9hcGlcL3YxXC91c2VyXC9zaWduaW4iLCJpYXQiOjE1NDM5NjYzOTQsImV4cCI6MTU0Mzk2OTk5NCwibmJmIjoxNTQzOTY2Mzk0LCJqdGkiOiJra1JLeUkwTnFuSW1qSmFCIiwic3ViIjoxMCwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.WFm09Znjt7CvdwPvUurVwBDPk50TCjnYj0ZrMe3Hy44
{
"user_id": "10",
"meeting_id":"3"
}
So now I want to register for a meeting, and of course I still have the user_id here, and that's on purpose if you remember this because I want to be able to register any user for a meeting. Now, of course, you could get rid of this functionality too, and just parse it to user from the token, but here this is done on purpose, so I'll leave it there. However, I still have to authenticate in order to be able to do that. And now I am registered.
```json
{
"msg": "User registered for meeting",
"meeting": {
"id": 3,
"created_at": "2018-12-04 23:05:16",
"updated_at": "2018-12-04 23:05:16",
"time": "2018-01-30 13:30:00",
"title": "Test Meeting",
"description": "Test"
},
"user": {
"id": 10,
"name": "monk3",
"email": "[email protected]",
"email_verified_at": null,
"created_at": "2018-12-04 23:13:44",
"updated_at": "2018-12-04 23:13:44"
},
"unregister": {
"href": "api/v1/meeting/registration/3",
"method": "DELETE"
}
}
```json
{
"msg": "User unregister for meeting",
"meeting": {
"id": 3,
"created_at": "2018-12-04 23:05:16",
"updated_at": "2018-12-04 23:05:16",
"time": "2018-01-30 13:30:00",
"title": "Test Meeting",
"description": "Test"
},
"user": {
"id": 10,
"name": "monk3",
"email": "[email protected]",
"email_verified_at": null,
"created_at": "2018-12-04 23:13:44",
"updated_at": "2018-12-04 23:13:44"
},
"unregister": {
"href": "api/v1/meeting/registration",
"method": "POST",
"params": "user_id, meeting_id"
}
}
Now I will try to unregister, and I do this by simply passing the token here to because upon unregistration the user is parsed from the token. And, as you can see, user unregistered for meeting, okay this should be from meeting, but besides that I think this looks very good. typo
So we really achieved a lot here. You now got a fully functional RESTful service built with Laravel, with authentication in place, with database access in place, with all the routes you see here on the left to create and sign in users, create meetings, update meetings, delete meetings, and register a user for meetings. So this really is a little application, a little calendar-like application on its own. And with that application you learned how to structure such a service, how to parse responses, which help Laravel offers you, how to connect to a database, and manipulate data in the database, and how easy it is to implement authentication.
Now what would be the good next steps? You might dive deeper into the authentication part, for example with the lecture I suggested, to get a better feeling on how to configure security measures, or how to configure the authentication measures in place here, or how to improve security of your service.
You might also have a look at one of the front-end tutorials on React, or Angular 2, or Angular 1 to get a feel on how to connect to such a service from a real application, instead of using Postman like we do here. So that would be good next steps, but you should have a solid foundation on how to build such services. I'm very excited that you took this tutorial with me, and I can't wait to get into the discussions with you, and see all the awesome things you're going to build with that knowledge. I sure hope to see you in other tutorials. Have a great time, bye.