Adding OpenID Connect Authentication to the Epic Stack
In this video, Kent C. Dodds demonstrates how to add OpenID Connect authentication to the Epic Stack. He walks through the authentication flow, shows how to manage connections, and discusses the implementation process. If you're a web developer looking to integrate OpenID Connect into your stack, this video provides a helpful guide.
Find the repo at github.com/kentcdodds/epic-oidc.
00:00 Hello folks, it's Kent C. Dodds and I am here in the hotel at React Rally and I want to show you OIDC, Open ID Connect support in Epic Stack. This isn't a built-in thing, this is just an example based on the Epic stack. And so I'll show you the way it works first and then I can show you how it works. So if I go to login and I say I want to log in with GitHub or log in with Google, looks like I gotta fix the icon there but we go to log in with GitHub.
00:33 And that takes me through the auth flow. I have all the environment variables set up locally and all that. So I say authorize, and I now go through the onboarding flow. So the application now knows who I am, gives me a suggestion on a username and a name, and I agree to the terms of service, and I say, remember me. And now I hit Create Account.
00:55 Oh, dang it. A user already exists with this account. I've tested this before. So we'll say 55 for no reason at all and now I'm logged in. You'll notice also the avatar is set as well and so I can change that if I want to of course and all of that.
01:13 I can manage my connection in here and this will tell me you cannot delete your last connection unless you have a password so I can go in here and create a password we'd say some password here create that password and now I can go into my manage my connections and I can delete this connection So I can now log in as either my password, so Kent C. Dodds, 55, and that works. Or I can log in with GitHub. And that goes to GitHub again and redirects me back because I've already authorized the application. And the application sees, oh, you're Ken Seedots.
01:56 We already have an account for you. Let us log you in. So Now I can also log in with Google. So if I say log in with Google, now this is kind of interesting. If I say log in with me at kentcdodds.com, then we already have an account on the site with me at kensydodds.com and so I am automatically logged in.
02:23 I don't have to onboard or anything because I already have that account. But what it also does, beyond just logging me in, is it will create that connection. So we go to Manage Connections and we'll see that I have my Google connection here as well as my GitHub connection, and I can delete these as I would so choose. I can also connect with more than one account. So I can say, hey, let's connect with Google again.
02:46 It'll take me through this flow again, and I can connect it as Kent.c.dodds and gmail.com and now I'm connected with that account as well. And then of course I can delete connections and all of that stuff too. So that is the the basic flow and there are of course a number of other things to consider here as well because we have two-factor authentication enabled. So I'm gonna make the screen go away for a second while I grab one password. Okay, great.
03:21 And so with that, I can scan this QR code, save and copy and then paste. And now I have two factor authentication enabled on my account so I can log out. And then if I log in with GitHub this is going to take me to the verification page so I have to go and grab my my new one-time password paste that in here and I can log in. So the two-factor authentication works for all of these as well and all of the the error cases that you would expect happen are all supported. Now most of this stuff is actually already implemented in the Epic stack because the Epic stack already supports GitHub OAuth.
04:06 And all the changes that I had for supporting OpenID Connect didn't require any database changes or anything with two-factor auth, like all that stuff just worked. And so for this example, what I did was I made one commit that just duplicated a bunch of the GitHub auth code, because a lot of the logic is going to be the same. And that worked just fine, but there was a lot of duplication. And so if you're planning on having more than one authentication provider, then I recommend following the pattern that I do by the end of this repo, which you'll be able to look at after this video. So what I do is I make the auth flow a little more generic for third party providers and then you implement an interface and with that you can then add any number of providers that you would like.
05:00 So the process of adding a provider involves going to connections.tsx, and if we were going to add, like, login with Twitter, for example, we'd say, Twitter provider name, and then we'd say, Twitter provider name, And then we're going to get a bunch of red underlines all over the place telling us, hey, you're missing some stuff. And so you could just run the types npm run type check. And that will tell you all the places that you need to update. In fact, there's really, oh, we didn't save the file. There we go.
05:39 In fact, there's actually only one place that you actually need to do a major update, or I suppose a couple places. So you have a couple of places in here that you'll need to do this, and then in the connection server, you need to add one of the providers, so the server side of these connections. So the way that this providers works is you have this auth provider interface where you have this get auth strategy. This is specific to a remix auth strategy. Building those is straightforward and there are many already implemented, but any auth strategy supported by RemixAuth will just work as part of this interface.
06:24 And then you have this handleMock action and handleMock callback. This is actually not required, but testing this sort of auth flow is very, very difficult. So we do actually have automated tests for this connection already. So if we go into routes auth and then our auth provider callback test, we have a bunch of tests for all of this stuff. So making this testable thing is an important aspect of all of this.
06:58 And so we have a couple specific things that you can provide for this action and handle mock callback so that you can kind of mock things out. This is not something you typically wanna do in your source code for most things, but this is one situation where changing your source code to make testing easier or better is actually worth the danger. So yeah, it's mostly the auth strategy, and then we have this resolve connection data. That's for that connections page that shows what user name that you have on GitHub and whatever. And so this is where you would interact with the API to say, hey, I have this provider ID, what is the user name or email for this particular user?
07:47 So, let's take a look at what this ends up looking like for a provider like this. So the Google provider, where you say get auth strategy, we're using OIDC strategy from web OIDC slash remix. And this is a remix auth strategy specifically. And then you provide just a couple of things in here that are necessary for this to work. So you have your client ID, you have your client secret, your redirect URI, which of course you'd want to set that to something for production.
08:19 Authorization scopes or params, so scope in particular here. Then the issuer. So all of this stuff is the sort of thing that you would actually find from whatever OpenID Connect provider that you're interacting with. And once you've configured all of this stuff, then it becomes a matter of taking the profile. You also get the access, or the, yeah, You get issuer requests, tokens, and some context, which you won't really need in here.
08:51 And so here I just say if they don't have a verified email, then we're going to just send them to login. We don't want to allow anybody to login who hasn't verified their email because then they could try to set up an account for an email that is somebody else's or something that would be ridiculous. So yeah, we have this piece here and then we return this profile which is all of the data that we need as people are signing up for an account on our app. And then we have this resolve connection data. So this is where you would talk to the Google API to say, hey, I have this ID for one of your users.
09:30 Give me information that I need to display them on that connections page so they know which of all the different things there are. You could also save that information. At this point, you could say, hey, I'm going to save their email address to my database. You could totally do that. The reason that I don't is because, at least in GitHub's example, they can actually change their username and that means that our data, or our version of the data would be stale.
09:57 And so we save the thing that cannot be changed and then we talk to the API to get the stuff they can. So that is the sort of thing that you would use this for. And then again, these two things are just some things necessary for mocking things out for local testing, which we can totally do right now. So I want to demonstrate how that all works. So if I hide things here for a second while I swap these tokens and everything out then we will be able to test this out.
10:30 Okay here you go. So I'll restart the server and we'll refresh here. I will log out and then I'll go to login. Now, what the devil? Oh right, We have a connection here that we are not going to be doing.
10:51 There we go. OK, great. So now if I say log in with GitHub, it's not actually going to go talk to GitHub, because we're mocking all of this stuff out. And it's going to send me right here to the onboarding flow page with some random user data in here. Going back to log in with Google, same story here as well.
11:11 And so the way that that works is we are using MSW to mock out API calls, and all of the API calls that are made for this authentication process are mocked out already. And so if we go to our tests and then mocks directory, Here we'll find the mocks for GitHub, where we're making fake users and all of that stuff, and then the mocks for Google, where we're creating all of Google stuff. With that, we're able to mock things out for local development, offline development, and testing, which is an important thing with something like this, because it is actually quite difficult to test when you've got this third-party Auth flow. So having all this stuff mocked out is pretty useful. Yeah, I guess one last thing, we have this env server that gives us a little type safety on our environment variables.
12:09 So if you add another auth provider, you're probably going to need secret keys and things like that. So this is where you would stick that. And once you have all of this set up and everything, I would recommend taking the default off of this so that it actually does throw an error if you don't have this environment variable set up. And also the env example this is where you would put your example environment variables so new people who are getting things set up know what environment variables they need if they want to really have the real experience. That said, because it's all mocked out, They can actually just copy this as a .env file without making any actual values.
12:50 And then you'd be able to just work locally without actually hitting the real APIs. So that is OpenID Connect with the Epic stack. A lot of the heavy lifting actually just comes from thanks to our good friend Sergio who built the WebOIDC package and RemixAuth. So huge Thank you to Sergio for making this as easy as it is. I hope this is helpful to you in all of the single sign-on stuff that you're doing with OpenID Connect and all of that.
13:27 And if you're doing something with authenticating with third parties, this hopefully will be really helpful to you. So thanks for joining, and I hope that you have a wonderful day. Bye.