You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: rails6/en/chapter04-athentification.adoc
+23-21Lines changed: 23 additions & 21 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ You can clone the project up to this point:
12
12
$ git checkout tags/checkpoint_chapter04
13
13
----
14
14
15
-
In this chapter things will get very interesting because we are going to set up our authentication mechanism. In my opinion this is going to be one of the most interesting chapters. We will introduce a lot of new terms and you will end with a simple but powerful authentication system. Don’t feel panic we will get to that.
15
+
In this chapter things will get very interesting because we are going to set up our authentication mechanism. In my opinion it's one of the most interesting chapters. We will introduce a lot of new terms and you will end with a simple but powerful authentication system. Don’t feel panic we will get to that.
16
16
17
17
First things first (and as usual when starting a new chapter) we will create a new branch:
18
18
@@ -29,11 +29,11 @@ The flow for authenticating the user through an API is very simple:
29
29
30
30
. The client request for `sessions` resource with the corresponding credentials, usually email and password.
31
31
. The server returns the `user` resource along with its corresponding authentication token
32
-
. Every page that requires authentication, the client has to send that `authentication token`
32
+
. For every page that requires authentication the client has to send that `authentication token`
33
33
34
-
Of course this is not the only 3-step to follow, and even on step 2 you might think, well do I really need to respond with the entire user or just the `authentication token`, I would say, it really depends on you, but I like to return the entire user, this way I can map it right away on my client and save another possible request from being placed.
34
+
Of course this is not the only 3-step to follow, and even on step 2 you might think, well do I really need to respond with the entire user or just the `authentication token` ? I would say, it really depends on you, but I like to return the entire user, this way I can map it right away on my client and save another possible request from being placed.
35
35
36
-
In this section and the next we will be focusing on building a Sessions controller along with its corresponding actions. We’ll then complete the request flow by adding the necessary authorization access.
36
+
This section and the next we will be focusing on building a Sessions controller along with its corresponding actions. We’ll then complete the request flow by adding the necessary authorization access.
37
37
38
38
39
39
=== JWT presentation
@@ -44,9 +44,9 @@ When it comes to authentication tokens, there is a standard: the JSON Web Token
44
44
45
45
Overall, a JWT token is composed of three parts:
46
46
47
-
- a *header* structured in JSON which will contain for example the validity date of the token.
48
-
- a _payload_ structured in JSON that can contain *any data*. In our case, it will contain the identifier of the "connected" user.
49
-
- a *signature* that will allow us to verify that the token has been encrypted by our application and is therefore valid.
47
+
- a *header* structured in JSON contains for example the validity date of the token.
48
+
- a _payload_ structured in JSON can contain *any data*. In our case, it will contain the identifier of the "connected" user.
49
+
- a *signature* allows us to verify that the token has been encrypted by our application and is therefore valid.
50
50
51
51
These three parts are each encoded in base64 and then concatenated using points (`.`). Which gives us something like that:
52
52
@@ -71,7 +71,7 @@ Once decoded, this token gives us the following information:
71
71
72
72
NOTE: For more information about JWT tokens I invite you to visit https://jwt.io[jwt.io]
73
73
74
-
This has many advantages such as sending information in token's payload. For example, we may choose to integrate the user's information into the _payload_.
74
+
This has many advantages such as sending information in token's payload. For example, we may choose to integrate user's information into the _payload_.
75
75
76
76
=== Setting up the authentication token
77
77
@@ -100,9 +100,9 @@ The library is very simple. There are two methods: `JWT.encode` and `JWT.decode`
100
100
=> [{"message"=>"Hello World"}, {"alg"=>"HS256"}]
101
101
----
102
102
103
-
In the first line we encoded a _payload_ with the secret key _my_secret_key_. So we get a token that we can simply decode. The second line decodes the token and we see that we find our _payload_ well.
103
+
In the first line we encoded a _payload_ with the secret key _my_secret_key_. So we get a token we can simply decode. The second line decodes the token and we see that we find our _payload_ well.
104
104
105
-
We will now include all this logic in a `JsonWebToken` class in a new file located in `lib/`. This will allow us to avoid duplicating the code. This class will just encode and decode the JWT tokens. So here is the implementation.
105
+
We will now include all this logic in a `JsonWebToken` class in a new file located in `lib/`. This will allow us to avoide duplicating the code. This class will just encode and decode the JWT tokens. So here is the implementation.
106
106
107
107
.lib/json_web_token.rb
108
108
[source,ruby]
@@ -124,8 +124,8 @@ end
124
124
125
125
I know that's a lot of code but we're going to review it together.
126
126
127
-
- the method `JsonWebToken.encode` will take care of encoding the _payload_ by adding an expiration date of 24 hours by default. We also use the same encryption key as the one configured with Rails
128
-
- the method `JsonWebToken.decode` will decode the JWT token and get the _payload_. We then use the https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html[`HashWithIndifferentAccess`] class provided by Rails which allows us to retrieve a value of a `Hash` with a `Symbol` or `String`.
127
+
- the method `JsonWebToken.encode` takes care of encoding the _payload_ by adding an expiration date of 24 hours by default. We also use the same encryption key as the one configured with Rails
128
+
- the method `JsonWebToken.decode` decodes the JWT token and gets the _payload_. Then we use the https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html[`HashWithIndifferentAccess`] class provided by Rails which allows us to retrieve a value of a `Hash` with a `Symbol` or `String`.
129
129
130
130
There you go. In order to load the file into our application, you must specify the `lib` folder in the list of Ruby on Rails _autoload_s. To do this, add the following configuration to the `application.rb` file:
We have therefore set up the system for generating a JWT token. It is now time to create a route that will generate this token. The actions we will implement will be managed as _RESTful_ services: the connection will be managed by a POST request to the `create` action.
154
+
We have therefore set up the system for generating a JWT token. It's now time creating a route that will generate this token. The actions we will implement will be managed as _RESTful_ services: the connection will be managed by a POST request to the `create` action.
155
155
156
156
To start, we will start by creating the controller of and method `create` in the _namespace_ `/api/v1`. With Rails, one order is sufficient:
157
157
@@ -238,7 +238,7 @@ Failure:
238
238
Expected response to be a <401: unauthorized>, but was a <204: No Content>
239
239
----
240
240
241
-
That's normal. It is now time to implement the logic to create the JWT token. It is very simple.
241
+
That's normal. It's now time implementing the logic to create the JWT token. It is very simple.
242
242
243
243
.config/routes.rb
244
244
[source,ruby]
@@ -267,10 +267,10 @@ end
267
267
268
268
That's a lot of code but it's very simple:
269
269
270
-
. We always filter the parameters with the method `user_params`.
270
+
. We always filter parameters with the method `user_params`.
271
271
. We retrieve the user with the method `User.find_by_email` (which is a "magic" method of _Active Record_ since the field `email` is present in the database) and we retrieve the user
272
272
. We use the method `User#authenticate` (which exists thanks to the gem `bcrypt`) with the password as a parameter. Bcrypt will _hash_ password and check if it matches the attribute `password_digest`. The function returns `true` if everything went well, `false` if not.
273
-
. In the case where the password corresponds to the _hash_, a JSON containing the _token_ generated with the class `JsonWebToken` is returned. Otherwise, an empty response is returned with an `unauthorized` header
273
+
. If the password corresponds to the _hash_, a JSON containing the _token_ generated with the class `JsonWebToken` is returned. Otherwise, an empty response is returned with an `unauthorized` header
274
274
275
275
Are you still here? Don't worry, it's over! Now your tests must pass.
So we implemented the following logic: API returns the authentication token to the client if credentials are correct .
298
298
299
-
We will now implement the following logic: Each time this client requests a protected page, we will have to find the user from this authentication token that the user has passed in the HTTP header.
299
+
We will now implement the following logic: Each time this client requests a protected page, we'll have to find the user from this authentication token that the user has passed in the HTTP header.
300
+
301
+
//pas compris
300
302
301
303
In our case, we will use the HTTP header `Authorization` which is often used for this. Personally, I find it the best way because it gives context to the request without polluting the URL with additional parameters.
302
304
303
305
We will therefore create a `current_user` method to meet our needs. It will find the user thanks to his authentication token which is sent on each request.
304
306
305
-
When it comes to authentication, I like to add all the associated methods in a separate file. Then simply include the file in the `ApplicationController`. In this way, it is very easy to test in isolation. Let's create the file in the `controllers/concerns` directory with a `current_user` method that we will implement right after:
307
+
When it comes to authentication, I like adding all the associated methods in a separate file. Then simply include the file in the `ApplicationController`. In this way, it's very easy to test in isolation. Let's create the file in the `controllers/concerns` directory with a `current_user` method that we will implement right after:
As usual, we start by writing our tests. In this case, our `current_user` method will search for a user by the authentication token in the HTTP header `Authorization`. The test is quite basic:
328
+
As usual, we start by writing our tests. In this case, our `current_user` method will search for an user by the authentication token in the HTTP header `Authorization`. The test is quite basic:
Authorization plays an important role in the construction of applications because, unlike authentication, which identifies the user, authorization helps us define what he or she is allowed to do.
449
+
Authorization plays an important role in the construction of applications because it helps us define what user is allowed to do.
448
450
449
451
We have a route to update the user but there is a problem: anyone can update any user. In this section, we will implement a method that will require the user to be logged in to prevent unauthorized access.
450
452
@@ -477,7 +479,7 @@ class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest
477
479
end
478
480
----
479
481
480
-
You can see that now we have to add a header _Authorization_ for the user's modification action. We want to receive a _forbidden_ response if we don't .
482
+
You can see now we have to add a header _Authorization_ for the user's modification action. We want to receive a _forbidden_ response if we don't .
481
483
482
484
We can imagine about the same thing for the test _should forbid destroy user_:
0 commit comments