 All right, thank you all for coming to my session. It's called Deep Dive into Drupal A through a single sign-on example. So my name is Arlina Espinosa. I've been a Drupal user for almost seven plus years now. And I'm a Drupal developer with chapter three. I mostly deal with the back end. And I love Drupal. I guess that's why I'm here. So my history with Drupal goes a long way back. When I got started, I wasn't exclusively Drupal before I used to handle other systems, CMS, LMS, Moodle. And what got me really hooked into Drupal were the hooks, you could say. And I love Drupal because other systems, you were trying to extend them to customize functionality and something else. And you kind of have to hack them. You just copy files. So you overwrote some classes if you were lucky. Otherwise, you overwrote some functions around there. And it just felt really, really hacky. So when I found out about Drupal, how you could create modules, how you could extend their functionality, I just fell in love with that. And it felt so great that I could create a custom module. And it would work almost perfectly with any other module that was out there with the community contributed. So it's a really nice feature that Drupal has, the fact that there were hooks that could be used to extend the system. But as you guys all know, Drupal 8 has changed. And I'm not going to go too deep into this. I just want to recap what's happened. We used to have lots of types of hooks. There were information hooks and alter and event hooks. So mostly, they either allowed one module to define metadata and tell other modules how they could extend their type of information. Like for blocks, you have block info, where your module could define other types of blocks. You have token info, where your module could define some other tokens. Entity info, where you could define your own custom entities. So those were just ways of defining metadata that were in Drupal 7 in previous versions. And there were the alter and events. So hooks to react to other events within the system, like hook in it to hook onto when the page was loading. Menu alter to alter the menu structure. There were tons of other hooks in Drupal 7 and previous versions. Drupal 8 is a bit different. So we've all been hearing the greatness and goodness that Drupal 8 has. That it's object-oriented, blah, blah, blah. That brings to it that we have interfaces, dependency injection, unit tests, and all those goodies that we have. And when I was trying to learn Drupal 8, I come from a computer science background, but I've been too deep into Drupal for so many years that you could say I was rusted in my OOP skills. So going back to Drupal 8 was like, let me try to recap all the kinds of concepts that are here. And Drupal also has a lot of Drupalisms, so it's just something that you need to be learned as well. So I decided to learn with a project. Like, let me try to build a module in Drupal 8 that uses these new concepts. So when I went about and read about all the changes and research, the information that was out there, it was way too theoretical. Everyone was going on about what plugins were and the new architecture, and events, and plugin managers, and I was like, okay, how am I gonna use that in my modules? Or how am I gonna know how to extend another module from Contrib? So in Drupal 8, there were a few hooks left. There's still too many, in my opinion, but there's still a lot of hooks in there. And the architecture has changed. So the main idea of this session was to share with you guys my experience learning Drupal 8 with building this custom module. So my goal when I was building this custom site was to use Drupal Core and just try to have as slow as from Contrib as possible. So I was just using the core modules, the Vell admin tool, just to have like a nice UI. And my goal was to have a website where users could register using social login, either Google login or their Facebook credentials. And I did not want to allow creation of accounts outside of social logins. So that was just my goal. And for the first part, not allowing users to create accounts on the site, if it wasn't for Google or Facebook, it's really easy to just go to configure your site and say, oh, only administrators have to create accounts and no one can create accounts otherwise. So that's easy. And I just set out a couple of profile fields to the user entity, just so I could have places, a place to have the metadata in there. Where it gets interesting is when I was researching how to implement this social login thing. So it looks complicated. It's not too complicated once you get into that. And fortunately in Drupal, we have other modules that already deal with this kind of complexity. And I always try to reuse the contract module where possible, instead of maintaining custom functionality myself. But it's really interesting to get to know how some of these functionalities work. So I had an option when I was doing research to connect to Google. And they use OAuth and OpenID. They support that protocol. So I dove into that and I just wanna share with you guys how that workflow works behind these scenes. So on the first round we got our user and let's just say they try to login using their credentials, their Google credentials. So they'll go to your website. They'll type that in their browsers and they'll send them to, like the browser will request a page from your server. Your server will show a page while it will do a redirect to Google. And that's the page where it says like, oh, put your username, your password here, and authorize the application, blah, blah, blah. So the user will provide their credentials. The browser will send their credentials to Google. And if they're correct, then the server will answer with an authorization code. And it issues a redirect with that authorization code to your website right here. Your website, your server gets that authorization code and it uses this to ping Google and try to exchange it for a token request. And then Google responds with a token and your site is authorized. So it's not too complicated. There's a lot of arrows in there but it's the user like inputting their credentials, Google sends an authorization code and your server responds with a, it requests a token and Google responds with a token. And all of this is just OAuth 2. And what OAuth allows is authorization. So far our website, when it gets this authorization, does not have any clue who the user is or what his profile fields are, what he's email is or anything else. So that's the part that OpenID fills in. OpenID is based on OAuth 2. And very briefly what it does is, besides responding with unauthorization codes, it also adds another ID token which is a JSON web encoded token. And it just provides like the user information within that token. So that way your website can get authorized and it can authenticate a user as well. But as I mentioned, Google is great and there's an OpenID Connect module which deals with that bunch of arrows going in and out of Jupel and your website. So what I did was require and install the OpenID Connect module and I installed it and this is the interface that it provides out of the box. So because Google plays nicely with the OpenID protocol, it already has a Google plugin. So what I was doing just trying to map the profile fields that Google sends, the profile fields are called claims when you're talking about OpenID protocol. So it's just mapping the claims to the profile fields you've got in your Jupel site. So it's just mapping like the name, it's the first name, family name, it's called last name. So just translating fields here and there. And you need to create an application on Google so you can actually paint Google with an application code. So you go to console developers at Google, you create an application and you have to let Google know which of their services your application will be using. So you add Google a lot to client ID, it needs a bit of config, Google needs to know which redirect URL to use. So this is a redirect URL for that module, just open ID connect slash and Google. And just doing these two things would work with OAuth too, but because we're also gonna be using OpenID and we wanna request the user information, we need to enable the Google plus API for Google as well. Otherwise we would not get all the user information. So once we have that information, Google will give us a client ID in a secret, which we will just use that to configure on the OpenID module page. And we add the OpenID connect login block on the block interface and this is how our site looks. So now user that visits our sites has a block where it says login with Google. They click that button, it sends them to Google and there's the authorization and authentication request. So it tells you what permissions your application is requesting. If you allow, then it does the redirect to your site and you can see the user has been logged in and it pulled in the profile information from Google into your Drupal site. So far it's just been site building and it was really awesome. I was like, oh cool, this is like, I haven't needed any code so far. I'm gonna need another extra project to learn code. Let me try Facebook. So Facebook does not use OpenID protocol. They've got their proprietary Facebook login workflow. Why did it not use Facebook login? I guess they're just like too popular and have their own product or just want the market share, I have no idea. But the Facebook login is based on OAuth 2 and it's actually pretty similar to OpenID. So that's where I found out I could get my hands dirty and start coding a solution and try to extend the OpenID connect module. So first of all, I needed to create a module from scratch. Using Drupal console to generate, bootstrap all the classes there, just boilerplate code. My module name is called example. You know, and all that I need is a info.yaml file for my example module. So this is my, you know, you place it in your modules, custom and the name of your module, example.info.yaml. I just declared that dependency on the OpenID connect module. That was like the only custom thing I was doing there. So I've got my module now and this is where the tricky thing and where the learning curve started, like getting interesting. I opened the OpenID connect module. I'm actually gonna pull it up over here, PHP storm. So there's the OpenID connect module. And there's, like if you're new to Drupal aid or object-oriented stuff, like there's so many files all around. And folders and folders within folders, et cetera. So what I found really interesting is going into someone else's code, into contrib and trying to figure out how to extend that module. So Drupal aid uses plugins, which is the preferred way to have your module be extensible for someone else. And I found out this module was using the plugin architecture because of a couple of things. So you can see there's an OpenID connect client manager class. So client manager class, a manager class, it's basically a class that it's in charge of finding plugins of a particular type. So what that tells us is that when there's a manager class, most likely that module is declaring a new type of plugin. So that's a clue in there. You can also see there's an annotation folder there. So they're declaring a new type of annotation. What's really a telltale sign if you open the client manager class, like if you just wanted to look into the code, is that big enough? So this is, when you declare your own plugin class, I will extend this type of plugin. This tells you like where to place it. You need to place your custom plugin within your folder in the source directory, in the plugin directory, in a folder called OpenID Connect Client. So that's the name like the folder structure that it should follow. And then the class that you create to extend this module should implement this interface. Drupal OpenID Connect plugin, OpenID Connect Client interface. And then it also tells you that your plugin has to have this annotation, OpenID Connect Client. So the annotation, this class described here, is described in the annotation class right here. So there's not too much in here. So you can just see that the annotation needs two fields, ID and label, and that's it. So I went like, okay, this does not look way too complicated. I think I can try to handle it. And then I found out there's something really interesting. A design pattern within most Drupal modules. So this module declares a new type of plugin, an OpenID Connect Client plugin. And as I mentioned, your plugin needs to implement a particular interface which was this interface right here. So if I open this interface, there's like five methods your class should declare. But most of the modules that declare any type of plugin already have a base class. That do that basically. So they already implement the base interface. And all you need to do is extend that base class that that module already provides. So you don't have to worry too much about trying to work it out. I mean, you could obviously overwrite any method you need, but usually they provide the base functionality as well. So it's just like blocks. If you've ever created a block in Drupal 8, you do not need to create all the methods, just a particular method that will display whatever information you need or overwrite a particular thing. So most of the modules that declare new types of plugins provide the base class that you just need to extend. So I decided to go ahead and create a new type of create my Facebook plugin for the OpenID Connect module. Okay, so that was me inspecting the module. But in case you guys are wondering like what is the plugin? How many of you guys have crafted Drupal 8 modules? You? Have you done plugins, services? Yeah, okay, so let's see your hands. So plugins as I mentioned, it's the preferred way of extending a Drupal 8 module. And it's like, it's a fancy name, but it's just, you can declare like any piece of functionality that is swappable like anything else. So blocks is a classic example. You can have so many different types of blocks, but they all work the same way. Drupal does not care what your block does, it just cares that it can call a certain method within your class, and it will just render it and use it the same way. So they're defined by an interface and the object-oriented terminology. So what that means is that Drupal can interact with them in the same way. And what types of plugins we have in there, we have like blocks are types of plugins, all the field types are plugin types, CKLiter plugins, text filters, OpenID Connect clients in this case. And it just means that the functionality, they all work externally the same, even though they do the things differently within, but Drupal can call them in the same way. So I wanted to create my Facebook OpenID plugin that extended the OpenID Connect client base class. So here it is. The class's name is Facebook. It extends OpenID Connect client base, which is a base class, and a couple of pointers in this class. So the first one is that this class, is this the font big enough for you guys at the back? Okay, let me open it up here. So this, the first thing that I wanna call out is this annotation here. So as we saw in the client manager class, the plugin needs to have this annotation defined. OpenID Connect client, and here it is, OpenID Connect client. And when we looked at the annotation class, it declared two fields, which were ID and label. So I'm just declaring the ID and label required for that type of annotation. The second thing that should be called out is where do you actually place that class? And we go back to the manager class and it said you have to place it in plugin, OpenID Connect client folder. So you can see I'm placing my class in the source folder, plugin, OpenID Connect client. So by placing it in there, we're implementing what it's called the PSR4 standard. It just means that if you place it in the right place, Drupal is, or PHP is gonna be automatically loaded. You don't need to let someone else know where it is or require, include, or anything else. Just place it there, enable your module and it's gonna be found. Your plugin is gonna be detected. Another thing is that if your module has some configuration, you might need to define some default configuration for when you enable your module. So in this case, I'm just declaring like no values for any like custom settings my module has. The settings were already declared in the base class. I'm not defining any other settings in my module. So what I did was basically copy from the OpenID Connect module, whatever they had in install. So just clone the Google configuration into my configuration. So it's just declaring no values in there for the default configuration. In any configuration you place for your module, it's gonna be in config install. So I just enabled my module and it got detected. You can see there's the Facebook OpenID Connect client in there. It's inheriting the same fields as the base class, the client ID and client secret. What's interesting about getting to work with Facebook was that a lot of the functionality, as I mentioned, was already provided by the base class. So what was different using Facebook instead of using Google, was that Facebook kinda named stuff differently. They don't have standard URLs for their endpoints. So I defined the endpoints in here. Let me just pull this up. So I defined the Facebook endpoints in here where you need to go for the authorization, for the token and to retrieve the user info. So this kinda took a bit of digging around in their documentation because I was so annoyed when I went to Facebook with their docs and I went looking for web documentation for their APIs. And when they talk about web, all they describe is JavaScript. I was like, okay, come on, PHP is a web language, not just JavaScript. So what I did was basically download their SDK for PHP and just grab around for URLs and try each one to see what they were doing. Stack Overflow also helped. So those three actually worked really well. And basically these endpoints, what they do is in the little diagram I showed you guys in the beginning, it's like where it's gonna paint for all that information, like for the use, for the token, for the user information and for authorization. And the only thing I overwrote was like the naming that Facebook was giving to stuff. So like when it retrieved the user info, like you needed to have another bearer token, like in authorization instead of providing it in a different way. So it's just like formatting the request that it's sending to Facebook before sending it out. And like Facebook has their own claim format instead of saying first name, they say given name, family name, so it's just like different naming that they have. And the ID token, the one that provides the user info, like they call it a different way as well. So it's just like parsing their format into what the OpenID protocol requires. At the end of the presentation, I have a link for the source code of the module, it's on GitHub if you wanna look more deeply into that class. So you do the same process for Facebook. You need an application to actually start using the OpenID Connect stuff. So you go to developers Facebook with your account, your regular Facebook account, and you can create an application. Facebook is a bit particular, like what are you gonna be using your application for? So you need to say it's an app for pages, and again, what kind of products will you be using in your application? So you select Facebook login, and you enable client OAuthLogin, you also need web OAuthLogin, and they'll ask for the redirect URL, which is this. Once you provide that, Facebook gives you the client ID and client secret, you just plug it in, there you go. Now when the user and anonymous user visits your website, they have two options, login with Facebook or login with Google. And if you login with Facebook, it sends you to Facebook to authenticate and authorize, and you accept and you go back to the Drupal site and you can see it's holding user info as well and you have a user account. So I was really happy. But there were still a bunch of concepts I hadn't applied yet, so I was like, okay, what else can I do with this website that would make it simpler for the user? And I decided that I wanted to create a social login page. So I did not like the user slash login because that presented the form for Drupal user and Drupal password, and my users are not gonna have a Drupal user and Drupal password. So I decided to create a new path, a new page within Drupal that would just present the login block. So how do you do that? So you need to generate a controller. So I'm using again Drupal console to bootstrap the other classes boilerplate code. It'll just ask me like for information, like on what module do you want your controller? Give me a name for the class. What method within that class should I call? On what route should I respond to with that particular method? Title, whether you wanna load services from the container. In this case, I did because I'm gonna be using a form builder, I will be reusing the login block that's already provided by the open ID connect module. So no need to reinvent the wheel, I already got a working form in there. And it creates two files. It will create a controller in the controller folder and a routing file. So this might be a bit too small, so I'll put it up over here. So the routing file, whenever you provide a new URL, a new page on your module, you'll find it in the routing file. So this is actually pretty nice. It's something I really like about Drupal 8. You've got your path for your page, but that path actually has a machine name, you could say. In Drupal 7, if you try to reference another route, you have to reference by the full URL it had within the system. So if someone tried to override that path, you could get in weird, complicated ways where it was not that path anymore. Sticky situations. And now you can just reference the path by the machine name, so there's no confusion anymore. And all that this is declaring is that this path will be found here. And whenever you visit that page, it will call this controller class, SSO login controller. It will call this method within that class. And you can define any access control stuff in here. In this case, I want to be available for everyone, so I just declare true. But you can have other conditions, like only anonymous users, only authenticated users, or have more complicated information in their other conditions. Now if we inspect the actual login controller, the class that will respond when you visit that page. So it's actually quite a simple class. As most stuff within Drupal, it extends another base class. So it extends controller base. And those you do not need to worry about, basically it's just the bootstrap, the boilerplate code. So the only thing I wrote was like this five lines of code. And the login page method, all it does is if the user is actually anonymous, then it will use a form builder service to display this form, the open ID connect form. So that's the one that gives us like two links to Facebook or to Google. And if the user is not logged in, then it will just redirect to the front page. So it was just five lines of code and I have a new working page within Drupal where you could visit a user slash login SSO and it will present that block within the page. So not too bad, just five lines of code. We have a new page. That was nice. And then they decided, well, wait, I have a user login, it's like a social login page. I do not, like everyone knows that URL, user login. I do not want them to type it in and like get a user login page. This is gonna be confusing. So I went ahead and decided that I wanted to change the default to hide it, you could say, the default user login path. So how do you do that in Drupal 8? You would use a route subscriber. So route subscribers are a special kind of services. Services are just functionality that's reused across the application, basically. So when you have a service that's tagged, it's just like a certain category of services that get called in a particular moment. So in this class it's tagged with an event subscriber because it's gonna be called when an event happens. So again, Drupal Console provides us with a route subscriber method which is really nice and neat. I ask you like, which module do you need this route subscriber for? What service, what machine name do you wanna give your service just so it can be called by other modules as well or your own module? Give the class a name and it will generate a couple of files or update them. And it will create the route subscriber class and it will update or create the services.yaml file. So the services.yaml file, I'll put it up in here as well. This is the last stuff we're interested in right now. So this is like the machine name of our service, the route subscriber, and this is the name of the class. You can see there's like, you would not need, once you create the, you use Drupal Console to generate this, you would not need to go in and edit any of this. You just need to know that it's the class route subscriber. So let's go look into the route subscriber class. So again, I just wrote three lines of code for this. This will change the user login page to another non-standard URL, which is non-SSO login. Just wanna make it ugly so people will not type it. And all I say is like, here's the collection of all the routes that Drupal has in this argument. I just tell it to have the user login page, which is the machine name of this user login page, and change its path to whatever new path I want to. So it's just three lines of code, and you can alter a path. That's also really neat. So now if you go visit user login page, you'll get a page knock down, because we just switched up URL. And then I decided to do something else as well. So just because it's very annoying to go to a page and then you get access denied, I decided to redirect that to the login page, to my social login page, not Drupal's default. And after they log in, I wanted to redirect them back to the page they were on before they got the access denied page. So you do that with an event subscriber. And again, it's a kind of service tack with event subscriber. So this is where it also got a bit interesting, because I'm gonna create an event subscriber, but the question is like, which event am I gonna subscribe to? So Drupal has some, not too great documentation yet, but there's a list of all the events that Drupal has so far. At least Drupal 4 and the official documentation. This is just an excerpt from that. It's not the whole list. But I found out that the event I needed was kernel events exception, because that's a 403 access denied error exception. So if you do not wanna go into the docs or you just wanna see which events are available on your site as well, there's another command that lets you see which events are available on your site. So you just type which Drupal console event debug and all the events your site has. But this is like if you already know what you're looking for, if you know what a kernel exception is, et cetera. So there's lots of events in Drupal and it's really neat. Like you got entity type events when they're update entities, delete entities. There's also migration events when you finish migrating. So you can just look into this page and find which events are there. So to redirect the access denied page to login, you generate an event subscriber using console. Again, which module is this subscriber for? Type in a service name, machine name, give the class a name. Which event are you gonna be responding to and which method is gonna be called within that class once you get that exception. Whether you wanna load any services from the container. So I was using the redirect destination service and it will update the services YAML file and it create a new class. So this is the new section that was added. Let me pull that up again in here. So you can see it's a machine name. There's the name of the class and it just gives it the argument of the exception. So let's look at that class, event subscriber class. Like any event subscriber that you need in Drupal has the same format. So get subscribe events, just let Drupal know which events are you gonna be reacting to. So in this case, you can respond to a bunch of an event within the same class. So just say it's an array actually. So you can react to as many events as you want and just say which method within this class is gonna be called for which particular event. So this case for kernel events and exception, it's gonna call this method which is on kernel exception. You get as an argument the event itself. So what I need to figure out, there can be so many different kinds of kernel exceptions. So I have to check the type, get the exception from the event extracted and just check if it's an instance of that access and it HTTP exception. If it's not that I don't care, proceed to whatever error handling Drupal default has, but if it is an access and it exception, then I just wanna save the page they were on to later on redirect them to that page. And I just like create a query out of that. And I check if the current user is not logged in, then redirect them to my social login page. So this was the machine name of that page and issue a redirect this response to that page. And once I log in Drupal because we're setting the destination parameter to redirect destination to such that, when they log in they will automatically get redirected to whatever page they were on. So again, it's just like 12 lines of code or so and we have another neat functionality in there. So now if you're not logged in, you visit a page, you would get the login page and you can see there's a destination parameter there with the path. So using all of this in Drupal has been really, has been really neat and it's been an exciting learning path for me. Just remembering all the OOP, like I found out that it's really organized. Code is really organized in Drupal 8. There's no more 2000 plus lines of code per module file. Weird naming of files in different places, trying to dig around someone else's code. So it's really nice. It's actually really, really nice. I don't know if you guys have any questions or comments at this point. Thank you. So I guess my main point was trying to show you guys that it's not difficult to go into another person, another contributed module, trying to figure out like the structure they have because now we have a better architecture in Drupal 8. And it's easier to extend someone else's modules using plugins, services and event subscribers. And if you get a little more advanced level you can actually try implementing like a new type of plugin. But just looking through someone else's code it's not too intimidating and it's fun. Hi. Thank you. How did you figure out which event to subscribe to because I think that list will extremely okay. Yeah, I think right now the list is bigger when I got started with this module. It was not so big. So I just reading the docs and like people commenting on Drupal issues. I did not have a clue back then. Now it makes more sense. Like it's a kernel exception because like the HTTP code it's an exception. But you just kind of need to know like what's there. There are not too many probably like around 30 events in Drupal right now or ish, 30, 40 ish. So it's manageable. The redirect. So before doing this presentation I also got started like on a way more technical presentation about what types of architecture models or design patterns were available in Drupal 8. And I had seen there were events, events, subscribers like so many different types of conceptual ideas in Drupal 8. So I had an idea of what a route subscriber was already. But it's a fairly frequent thing in Drupal 8 you could say. So it's going to be used fairly frequently. I think that's not a complaint, I don't know much about it, but I do a lot of work with FBO off for a good concept. And I'm just curious like was there any interest in looking at it to make some of this happen or is it? So that's a really interesting question. I would love to get it out of in contrib like for open ID connect module. But the fact is that Facebook does not officially support this. They've got their Facebook login products so they want you to use a Facebook login product. So it might change like this URLs or have some other breaking change that I'm not going to be aware of fast enough. So I could publish this, I mean it's on GitHub but I'm not sure it would be a good idea to pull it out on contrib, an official contrib module and have people depend on it. To do this first, why wouldn't you have used some of the code from the Drupal 7 module? Oh, because I want to learn Drupal 8. Yeah, you can say it's in here. I'll learn how to migrate on 7.8. I would look into that but a lot of the code in Drupal 7 is just structured way differently. And it's a lot of overhead to maintain. Not all of them, I think a big chunk of them are, that's why it's taking a little bit too long. Just because it's a great opportunity to not carry on technical depth and have a pause and rethink what we're doing. So if... It also, Drupal 8 provides a lot of push-pulling out of Drupal 8. It's a good point, you can be heard. Drupal 8 provides a lot of push-pulling out of Drupal 8. So when you say it's a bigger lift from, say, go on Drupal 6 and Drupal 7. And Drupal 7, Drupal 8's a lot bigger lift. A lot bigger. So, yeah. It's pretty much there. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Thank you guys. Just a quick reminder about Drupal sprints tomorrow. There's going to be pretty interesting stuff happening there. And just thank you all for coming. Please provide big feedback. I'm a calm child today. I'm coming in from Java. It might be easier for you because we're out of bus in Drupal. We're like so stuck doing like functional programming. Yeah, I was wanting to know about the unmodified stuff in the public subscribe.