2.6 Ticket Granting Cookies, Part 1
Now that we have login tickets in our system, let's move on towards another asset that's just as fundamental: the ticket granting cookies. They are the gateway towards a successful authentication across multiple services.
1.Introduction2 lessons, 07:43
2.Creating the API Server12 lessons, 2:10:56
3.A Client Demonstration App2 lessons, 20:49
4.Conclusion1 lesson, 01:35
2.6 Ticket Granting Cookies, Part 1
In the previous lessons, we have been exploring the protocol specification in regards to logging in. We have managed to provide login tickets for our users to login. The functionality of the login ticket is to prevent exploits on the login process to increase security. That way the only mechanism for logging in is to use the application itself. You know, you have to go to the browser, go to the specific address in order to provide a login ticket which will then be used for authentication. The next step in the process is the actual authentication. How do we handle a successful attempt? As the protocol says, we need to have a session cookie being passed and then follow the behavior that's described further in the document. We're gonna cover the basic case of having a service parameter being passed in. And we're going to just redirect it as you will see in the response here. Section 2.2.4 tells us that on a successful login we're going to redirect the client to the URL passed in as the service variable, and that's it. Also, we're going to have to pass in a service ticket. You can see here that the request must include a valid service ticket. It should be passed as the ticket variable in the URL, which is described further in the document. In section 3.1 you will see the service ticket property. By reading it you will understand that it is very similar to the login ticket. However, it is only issued in certain circumstances. Namely, it is provided once an authentication has been successful. It also must be valid for one single validation attempt. Meaning, in the following URLs that we'll cover later in the course, it should also be invalidated. So once you validate the service ticket, it will be expired. Also, the format of the service ticket should begin with ST and it should also be probabilistically unique. We're gonna use the very same strategy as we did with the login ticket. In section 3.8, you will see the properties of the ticket-granting ticket. This ticket is issued and passed in as the cookie. So the contents of the cookie that's passed in is this ticket-granting ticket. It begins with TGT. It is also unique in time. And it should be secure, as well as all of the other tickets. This lesson is all about generating ticket granting tickets and service tickets, in line 73. We're going to start with the first one and basically all it does is, it accepts a user email. A password, a login ticket which we already have, as you can see on line 42 at the top, and then, the service parameter. What I want to do is generate a ticket granting ticket on a successful authentication attempt. The same goes for the service ticket. We're gonna do exactly the same thing, and a service ticket instance should be expected from the service. With that said, let's run the tests right away. Judging by the look of the test, you will see that the ticket-granting ticket message isn't present in the login class. So, we'll have to implement it. Let's go to the login service and make sure that we generate that ticket. So if we have a valid authentication, or in this case if we don't have a valid authentication, we're going to expire that login ticket. Well, what we want to do is cover the case where the authentication is actually valid. So we're going to turn this into a positive condition and an else condition for the previous one. So if authentication is successful, then we're going to generate a ticket-granting ticket, which I'll define just below. Let's do that. I'm going to define that method, so generate_ticket_granting_ticket. And all we want to do is create that instance. So I'll create a TicketGrantingTicket like so. I'll pass in .new, and the name should be something in the terms of a digested hash, so I'll just do that. I'll copy what I had before, but instead of passing a password, I'll create a new instance of time, pass two string to it. And we will have a brand new hash that's going to be the name of that ticket. Also, let's make sure that it has TGT prepended to it because the specification tells us that. Let's assign this to a ticket_granting_ticket, there you go. I'll call the save method on it, so that it gets persisted in the database. This ticket granting ticket is also necessary to be registered in the database, because we will need to confront it later on when logging in with that ticket. Should someone forge a ticket-granting ticket, hacking into the system or something like that. That person will effectively not be able to do so because we are the ones that generate that ticket, and it gets registered in the database. Notice that if I run the tasks now, it's going to complain because we don't have that model yet. Let's do that. I'm going to open a new file under lib/api/models and then ticket_granting_ticket. There you go. I'm going to define that class, so TicketGrantingTicket. It is also going to be an ActiveRecord model for the reasons that I've just mentioned. Okay. Now, we're gonna have to edit a new file under lib/api/migrations and then a new one. So create_ticket_granting_tickets. Okay, so let's define that, Create TicketGrantingTickets. Let me just perform this really great trick. So it is going to inherit from ActiveRecord Migration as usual. We're going to define a new change method which will create a new table for us. So the table is going to be called ticket_granting_tickets. We'll pass a block to it. And the attributes that this table will have are, for example, a string with the name, and we will make sure that this will be not null. We're also going to have the timestamps and for now, this is it. We're going to focus on details later on because the only thing that we will want to store is the name. Let's go ahead and run the migrations again. Oh, it seems that we have a syntax error, so the string should be called name with a colon behind and the option right next to it. Let's run the migrations now. And there you go. Now we have that table. We can run the tests and see what the next step should be. Okay, so the model is still not loaded. Let's go to the actual API file and require that new model. Inside I'm going to switch this to ticket_granting_ticket and the run the tests now. There you go. Now the ticket_granting_ticket message is not available in the login service. At the top of it, I'm going to create a new attr_reader for it. There you go. Run the test again, and now the test is green. Okay, so now we know that the ticket-granting ticket will always be generated if the authentication is successful. One thing that I've noticing though is that the login ticket is expiring only if the authentication is not successful, which is not correct. We should always expire that login ticket. So I'm just going to remove that from the if condition. Run the test again to see if anything came up wrong. And there you go. This is pretty good. We are following the protocol guided by tests, and as you can see, we are trying to complement our code with good naming. It's very easy to understand what's going on in the code. Now, let's go back to our tests. I'm going to comment this one out. Just fold it up and focus on the next test. We want to generate a service ticket, too. So we're going to do a very similar procedure regarding the service ticket. So let's do that. Let's run the tests now. And as you can see, the service ticket class isn't defined. Well, as you can tell by the method, you can see that we still don't have anything regarding the service tickets. We know for a fact that the service ticket should be generated whenever an authentication is successful. So, we're going to generate a service ticket as you can see here. And similar to the ticket_granting_ticket, we're going to create something similar for the service ticket, as you can see in line 50. We'll define a new service_ticket variable with the specific name ST- and a new hash, the same mechanism as we are doing with all of the other tickets. Finally, we're just saving the service_ticket. Okay, so this is one step, but if we run the tests now, you will see that this still won't work. Let's create that service ticket class. For that reason I'm going to create something in these terms. Let's open a new service_ticket model. There you go. Let's define it. So ServiceTicket which will inherit from ActiveRecord::Base. And that's all there is to it in terms of the model. Then in regards to the migration, we're going to do something really similar as well. So I'm going to go to the migrations folder and create a service tickets migration. Similarly to the other file, I'm actually going to read that content. So hold on just a second. We are still going to create a table with a similar name, in this case, just service_tickets, which will also have a name and a set of timestamps. Let's just change the name of the classes, so in this case it will be CreateServiceTickets. The last step is to require it in the API file. So we'll include service_ticket. There you go, and now all we need to do is run the migration. Let's press Enter. There you go. Now we can run a test and see what we should do next. Okay, so the service ticket is still not there. The message in the log in service. We do have that instance variable though. So we just need to add in the attr_reader. Running the tests now we should have a green test. And we do. Notice we still have two skipped tests. These are the lasts ones that I wanted to focus my attention on. So I'm going to close these up and focus on the remaining two. These are here for a purpose. We want to log a user in if he already has attempted a good login.