Limit Access to Web Pages for Twitch Subscriptions
with Jason Lengstorf
To allow Twitch subscribers special access to additional functionality, we can create a web page that can only be accessed by active Twitch subscribers. In this episode, Jason will figure out how to make that work!
Resources & Links
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON LENGSTORF : Hello, everyone. And welcome to another episode of Learn With Jason. Today, on the show, we're going solo, again, we've had a couple scheduling hiccups this week. I promise we're going to have a lot of fun today. I thought today would be a fun day to plan with an idea that I've had for a long time, which is to try and use Jamstack Off.
So, okay, let me� let me maybe contextualize this a little bit. Here's what I want to do, let's talk about the outcome and then we'll step back to the "how." What I want there to be true is I would like there to be a dashboard that is somebody could open if the stream is line if you're a sub. I would love for there to be a way to open up the dashboard to the side and you would have access to maybe a soundboard or maybe I do polls or maybe you could, like� maybe we'll limit the submarine controls to just the subscribers. Maybe there will be new things that we build and the idea is that we can make this into kind of a fun� like, a fun way to� to interact with the stream, if you're a sub.
And, give us some� some abilities to, like, play a little bit more and basically give� give people a little more access by being subs, that is maybe a little too noisy or a little too dangerous otherwise. Like, being able to post a question or being able to� to� I don't know. Like, interact with something with texts input versus just being able to emote. There's always the risk of somebody� somebody anonymously abusing a feature when it's open. Kind of doing a little bit of stuff that I think is little bit more fun, but less safe and for now, at least, we'll see how well this scales. For now, at least, letting the sub being the filter. If somebody wants to pay $5 to troll, they will.
Yeah, so the "how" of how I want to do this is Twitch has an API and the Twitch API is good, getting better. They're always working on it. I've been happy with the Twitch dev team, they seem to be paying attention and listening when people are asking for things so, you know� it sounds like we do them to� hopefully make some improvements on the moderation and the protections for� for, you know, the underrepresented folks who are streaming, it seems like we got a lot of problems with harassment and bot rates and hate rates. Twitch devs, listen up. Help.
But, you know, around some of the other features, around being able to access the API, get information about your stream, update things like that. That API is steadily improving and it's been pretty wonderful, right? So, my goal, today is to take advantage of� well, here's my tentative plan. I haven't researched how to do this. We might have to call inaudible as we go. Twitch as OAuth so you'll be able to log into learnwithjason.dev. You're going to get an access token that we can then use to make a request for your subscription status on learnwithjason.dev.
So, if you are a subscriber, or Learn With Jason on Twitch, maybe we can show things no matter what, if you're a Twitch user and show things additionally if you're a Twitch subscriber. Maybe there's a mod controls. Maybe we can put the mod queue right into the dashboard.
We'll start with OAuth so people can log into learnwithjason.dev. Once we get that, we'll be able to show that. It'll show, you are logged in or you're not subscribed or not subscribed to my Twitch channel. Then, based on that� you know, all this is assuming that we get as far as I want to get. Then, based on that, we'll be able to put in some logic and show some capabilities.
Now, what I don't think I'll be able to today. I don't think I have the necessarily infrastructure to allow calls to be made to the stream. So, I don't think we'll get anything where, like, subs, today, will actually be able to do something from this dashboard. In a feature stream, I will figure out how to make it possible for that dashboard to make calls to the Learn With Jason stream.
So, let's start with OAuth. Let's head over to the home page, here, and let's do a quick shoutout to the sponsors. We're going to start by talking to the captions. We've got White Coat Captioning here, today, and that is always very welcome. It's available on the home page, if you want to read along as you go. And that is possible thanks to Netlify, Fauna, Hasura and Auth0, making this accessible to more people, which means a lot to me.
Let's see, look! Inception, it'll go forever.
Anyways, that's� oh, and, thank you for the subs. I appreciate it. Means a lot to me. And hopefully we'll be able to do something fun with that today. So, let's play� what I'm going to do, today, is I'm just going to start digging into these docs. Here's my thinking, I've got my learnwithjason.dev. Let's make sure I have the latest. What? What's different in here? Oh, okay. Yeah, you can be in here. Oh, wait, I don't need any of that, do I? So, I'm going to reset...okay. Then, I'm going to run an NPM to install and get rid of that package lock. Oh, thank you, Cynthia, for the sub. I appreciate it. Got a hype train. 94% of a Hype Level 1. I think you get emotes for that. I get emotes for that. I like emotes.
16 months? Yeah, both of you, at 16 months. Cynthia, thank you for the prime sub. This lighting setup is a labor of love. I've got, you know, a light� let's see, there's one here, right? Then, there's one here. And then there's one here. And then there's the LEDs behind me, so it's taken me a while to get this set up. This has been my pandemic project, is trying to get this lighting right. [Laughter].
Happy about that. What� what are you doing right now? What's going on? Parse error. What? What on earth is this? Let's remove node modules and let's remove Package JSON. Whoa! That's not what I wanted to delete. Remove package� package, lock JSON. Okay. Oh, wait! Dammit! Okay. I know what's happening now. I just need to remove this one because we don't actually need it. Okay. So, now� okay. We're clean, so I'm going to check out a new branch and we're going to call this one "feature." And we'll call it, like, "Twitch OAuth subs only." All right. So, I'm going to open this up and what I want to do is I want to create a page that's going to let us do, um, the OAuth flow. So I'm going to start by creating a page here. And this page is going to be called..."subscribers." How about that? And, let's take a look at how pages will actually work here, I kind of forgot. So, let's� let's look at, like, all of these are the same. Is that right? Okay. It's been a while since I've actually used this thing. So, let me get into subscribers. We don't need any of these components, so we can skip those.
We're going to say "subscribers only." And then, let's� let me get rid of all this. We don't need it. Okay. And then, let's run "develop." What?! Try that one more time. Oh, that's great! That's great, Cynthia. I hope your students are� are learning Netlify functions. I hope they're having fun with them. I very much enjoy the� the Netlify functions. I'm having a lot of fun with them. Y'all really going to let this hype train die at Level 1? You've got 55 seconds, make them count! What is happening right now? Okay. I might stand up just a little side page for this, because I don't know what is happening. Hmmmm...
Yeah, what are you doing? Something has gone weird, because� like, this was building earlier today. I literally just built my site. But let's not worry about it today. Instead, what we're going do is create a new repo. We'll call this "Twitch Subs Only." Move into it. And then I'm going to get "init." And here's what I want to do, I want to do� let's get into Vite here. We'll do "Vite, getting started." We can NPM Vite. And that'll let us pick any of these. So, let's do� let's do Preact. So I'm going to NPM "init Vite at latest." I think it'll just let me choose. Wait, wait, wait, wait, wait. Can I get it to� project name, and just have it go in here? Yes. Oh, invalid package name. It needs to be� let's go with "Twitch subs only." We're going to use Preact. I don't want TypeScript because I don't know it well enough. So, let's run NPM Install. Okay.
And then, let's open that and close this one down, here. And what do we got? We've got our index.html. It's going to go there and then we've got this app.jsx. This is our actually app, it looks like. And then this just runs it. Okay. All right. We'll just work inside this doc and make this the page that we� that we want to run. So, I'm just going to clean out all the stuff that we don't need. And we'll say, "subscribers only." Take the props out, we don't need those. Take the logo out, don't need that. And then we can just kind of clean up some of this stuff we're using. Are we using the index.css? Yes, we are. I'm going to run Netlify Dev and see if it works. What happened? Cannot find module update notifier. Let's use Node 16. That's probably going to make my life easier. I feel like I broke Node 14 on my computer.
Oh, nice! I was wondering if this is going to work. It knows Vite. That's useful. We've got the Subscribersonly page. It's purple. Is that the same color as Twitch purple? Oh, not quite, but that is pretty dang close.
I want to figure out, um...I want to figure out how to get some OAuth. Building on Docker on Raspberry Py? I don't know. I let� you know� Netlify runs hightraffic sites for free. You can run it on Render.com, or Digital Ocean or AWS. If you want to build it at home, you're going to want to talk to somebody that's not me.
You got your Corgi duck! I'm so happy. And the Corgi duck is so happy, too. I'm so happy about that. [Laughter].
Let's do� let's do� let's do some OAuth. I'm going to go to the API, here, and figure what we actually need. Authentication. I want to register an app, so let's start there. I click this button and it opens it in a new tab for me. So, we're going to call this "Learn With Jason." Do I need...a redirect...when testing locally, you can set this to local host. Okay. So, I'm going to set one to� what's my� my Vite. Local Host 888. Let's set it there, for now. And then we'll figure out what to do about that later. I'm going to build a website integration. It's not with an organization. And I'm not a robot. Oh, it's boats. Oh, no! This is my greatest fear. Okay. So, that one's a boat. That looks like a boat. It's got a be a boat in this photo, right? Probably going to tell me I'm a robot. That's a trash can. Are you a boat? Hahahahaha, first try! I love it. Can I create this now? Is this going to show my tokens? Not yet. Okay. Is this going to show my tokens? There's a client ID and I'll need a secret.
Okay. So, let's get the Client ID. Let's deploy this and we'll run it as we go. So, I'm going to create a new repo called "Learn With Jason Twitch Subs Only." I'm using the GitHub CLI.
You hackers! You dirty hackers.
JASON LENGSTORF : That was loud. Okay. So, then we� we've got that going. So, that, I can push. Oh, no. Okay. How do I link it to the one that I just created? Remote, add origin and we'll say, like� there's probably a way to do that easily. GitHub and LearnWithJasonTwitchSubsOnly.git. I'm going to Git Commit everything and say, building a subs and page and push. "Git Push." Okay, that worked. Then, let's initialize this. I'm using the Netlify CLI. Now. I'm going to create and configure a new site. We're going to call this "Twitch Subs Only." Good. It's going to run Vite Build. We can put that in a Netlify Toml. All right. Okay. So, that puts us in good shape.
And, what that also means I can do now is we'll say "Twitch@� what is it going to be called? Twitch Client ID. And that's going to be set, that value. Right? And then I also need to set Twitch Client Secret. That one, I'm going to do offscreen. So give me just a second to pull that off so that I don't accidentally give away all my secrets. So that's done. I now have my secrets. And I'm going to� I think I'm just going to close this window, actually. So, we might need to go back there. For now, we've got that set. And now when I run this� when I run Netlify Dev, we have access to the Twitch Client ID and the Twitch Client Secret, which we'll be able to use in the serverless functions.
So, let's figure out how to actually use those. So, I got my token. Okay. So, for getting a token let's read about this. Validating requests...we can just validate whether or not the token is good. Fine. And that will tell us� oh, look, this is already telling us what we need. We need Channel Read Subscriptions and then we can "registration." Client IDs. We did that. Types of tokens. So, I want authenticate. Get details about your user. That's what I want. I want you to be able to log in, as yourself� or maybe it's a user access token? No, because I don't really need to make access� it makes requests in the context of an authenticated user, you need to access a user token. Okay. So, to get a token, we're going to use the authorization flow. Maybe? No, I think we want the authorization flow, because we can put this� we can put the token in a HTTPonly cookie and use serverless functions to make requests. For the user access token, we can do an OAuth Authorization Code Flow. All right. So, to do that, we are going to set up a request to this endpoint, here.
So, let me grab this one...and let's go down here and we'll just say, um, "ahref=." Register redirect URI is HTTP Local Host 888 and the response type is going to be a code and the scope� I think all we'll need for now, at least, is the� the channel read subscriptions, which� is there a list of scopes I can see? Scopes...um...all right. So, I want to be able to read subscriptions and I think that's all I really need, for now. Well, I do want to be able to get, like, the basics. I want to be able to see your� um...your username. But I guess that should come back, no matter what? User edit, user follows, user read. User read subscriptions. You have an authorized user subscribed to specific channels. That is what I want, so this is the scope we'll use. Stick that one in there. We'll see what we get back.
The Client ID, because that's not private, I might just grab that. Let me go offscreen, real quick, and bring that back up and see if it'll give me the token I need. Let me make sure it's not showing� yeah, okay. It's not showing the secret anymore. I'm just going to do this so I don't have to figure out how Vite managing clientside tokens. Right. So, that should let us start the O Auth flow. If we go out here and look at our app, this looks terrible, but it's going to do what we want, which is to log in with Twitch. You're about to leave Twitch. So, we got a redirect mismatch, error description, parameter redirect URI does not match registered URI. That's untrue...do I need to include the trailing slash, because that's going to be� oh, my goodness! All right. So, let's just copy/paste. Let's try that one more time...okay. So, because my� so, apparently, the slashes very much matter in this and then I'm going to� clicking "authorize" will allow Learn With Jason to get the details of your subscription to a channel. Okay. I like that. It's only letting me do what I actually need, that seems like a good way to manage scopes. Requesting your public Twitch account information, after authorizing you'll be redirected. It gives me this code, and stuff like that, and so this is the part that we then have to manage, which once we get that back...we will authorize. They'll be sent back with a code and then I need to...authorize 2.0 code is a 30character generated in exchange for an access token. The response includes the parameter. So instead of doing this as a clientside thing, let's create a serverless function for it. And I'm going to put this Netlify Functions. Let's put in a new file and we will call this..."twitch OAuth.js." And that's going to be export async. I want to use [Indiscernible] here so I'm going to update my Netlify toml to use Build. I think eventually, the intention is to make this the default. But for now, you have to, like, explicitly enable it here.
So, I'm going to turn that off. Let me save that. And then I can have this one, here, this will get an event. And the event is going to contain some stuff. The one that we care about the most is going to be this code. Right. So, what I want to get out of it is I'm going to get "code=event.querystringparameters." All right. And then I'm� for now, I think� just going to return a status code of 200 and a body of "okay." Right? And that'll get us started. Let's consolelog the code to make sure it's actually doing what we want. Okay. So, we've got that. I'm going to stop and restart because we just adding that function. I think it would have worked, but just to make sure.
So, we've got that. And then what I need to do is update this code to be Netlify Functions Twitch O Auth. I'm going to copy that because that's going to be our new redirect URL. Using that, then, what we can do is go back into Twitch Developers and I'm going to change this one and add it. Okay. So, that� please don't make me do more boats. Okay. Save. All right. I've saved. We've saved that. And, now if I log in, what we should get...is, let's go here...I'm going to log in with Twitch. I screwed something up. Redirect. Oh, I got to actually update our redirect in the link, as well. So, that's that. Let's go back here. And try it again. And this takes us right through, because we've already OAuth' d. The next step, here, is going to be looking� getting token. So, now that we've got the code, we get one like this. We get the scope. Uhhuh. We make a request. That request is going to look like this one. So, let's go into our serverless function, here, and figure out how this is going to work. So, we'll put that here. All right. So, we start out with the� the token request. We get a Client ID and that's going to be my Client ID. And then we're going to make this one "process.m.twitchclient.secret." The code will be the code. Authorization code. And the redirect URI...I don't like that it sends you to the same redirect URI. Oh, I guess I can� I can register another one though. Okay. So, what this is going to give me back, I believe, is an actual token. So, here's the access token. Okay. And so what I need to do with that is then set it as an HTTPonly cookie and that way, we'll be able to� yes. Okay. So, that way, we'll be able to� to validate� we'll be able to put the token into the browser cookie in a way that you can only access with HTTP and then we can make requests with serverless because they can read� it's an HTTP request. So that means that I need another function to actually, like, get the token and we'll register that one as a "twitchtoken.js." This will be a redirect URI so we can do the thing. So, let me grab this. Put this in here. What will come back is a JSONencoded access� oh, wait, wait, wait, wait, wait...I send a post. And it just respond� okay, so can I just� if I set Local Host 888, Netlify Functions, Twitch OAuth as the URI and then...so, we'll just kind of get each of these pieces here. This is going to be a gnarly URL. My autoformatting is going to make this so bad, get ready. So, we've got our URL and then the response is going to be� we'll need Node Fetch, so let me install that. And, I'm going to get [Indiscernible] await, fetch, URL. And then we'll get the response. And we'll get it back as JSON and so I'm not doing validation, here, because I want to just doublecheck this.
But, so assuming that works, what I should be able to do, then, is, um...just log this response and that should be a token that will let me read my channel subscription. Now, it doesn't have any additional permission so I'm not going to stress about showing that. So, if you want to use this access token to request my subscription stuff, I guess you can do that. But I'm about to show it onscreen, so don't waste your time. So, let's run this, again. Let me actually import Fetch. Fetch. Invalid response body. So that could actually be that the token just timed out. So, let's start here, log in� invalid JSON response. Let's figure out what went wrong here. We're going to say� this is going to be res. And then if we didn't get an okay response, we will return a status code of 500 and a body of� I don't know. Is there a res.error message? What if we just do that? Otherwise, we will await res.json and we'll log that. So, what we should get, here, let's maybe consolelog the res, just to see what happens. So, we'll get that and then I'm going to come back out here. Reload the page. Try to log in. Size zero timeout. So, what happened? We got...body passthrough. What does that mean? So, we got the body passthrough. There is no error. But...we got a� oh, I need to send it as a post! I'm a doofus! So, we're going to fetch it as method post.
Okay. Let's try that one more time...we got an "okay." We got a token. All right. So, then, let's� let's try this token and see if we can actually make a request here. So, I'm going to check a user subscription and the way that we're going to do that is hit...whoa! Hit webhook. Where did� just lost it. Um...check user subscription, there it is. Let's try this one more time. We are going to hit "get with an authorization token. "Let's open Postman because it's a little bit easier. And, I'm going to create a new one. We're going to get...we're going to set up an authorization header. And, that needs to be there and then our token. We're going to send that to...broadcaster ID and user ID. We need to check to see if I'm subscribed to a certain person. I'm a user and my User ID should be in here. It's not. Crap! What's my User ID. I don't have a User ID or a Broadcaster ID. How do we figure that out? Is there, like, a really easy way to, like, look this up? Not just, like, a button. Oh, is this going to do it? Okay. So, let's convert mine to a Twitch ID. All right. So, the User ID� and then what's a channel I'm subscribed to? Chris. So, let's see if I actually get what I need. What else do I need to send with this? Client ID. So, my Client ID is the Client ID that we have in here, probably? Let's try it. Is gift Broadcaster ID tier. Okay. So, it's not like a true or false. Let's find somebody I'm not subscribed to. What's a� what's a Twitch� like, who's a popular streamer? Like, I don't� I don't subscribe [Indiscernible] so, what happens when I try to do one of those. No subscriptions to that user. Okay. All right. That's useful. We can just check if the status is 404, that means I have a valid sub or not.
So, the next thing I want to do is I want to� instead of just logging this response, I think what I'm going to do is, um...let's see, how could we do this? We could set� we could just stringify the whole response as a cookie and that way, if later we want to get better at, like, doing refresh tokens and stuff, which I'm not currently� we're not going to have time for that today�
Holy buckets! Did that just work?
JASON LENGSTORF : We can Netlify Set cookie. There's a whole thing here. Using functions to set cookies. So, we can just set� we send back a header and we set the cookie. And, what is this cookie? [Indiscernible] serialize. So, let's use this library, because otherwise, we have to write it out by hand and I only sort of understand cookie syntax so we're going to install "cookie." And we can do here is we're going to import cookie from cookie and down here, we can turn our response into a cookie. So, we'll say...cookie=tokencookie. And that will be "cookie." Call it something...something identifiable. That's going to be the value. So, the value will be JSON stringify response. And then we can set some details here. So, we do want it to be secure. We do want it to be HTTP. The max age, we can set to the expiration, but maybe what I'll do...maybe what we'll do, to start, is, um, just use this� this two weeks. So, one hour� I don't know why� like, I get that� that convention. But I'm not going to do it right now. So, we need 14x24 and I would figure out the expiration on the cookie here. Or, wait, is that just the number of seconds? We'll do response.expiresin� good. Okay. So that's good enough.
For the body, we continue to send "okay." But we'll send headers and those headers will be to set cookie. And then I want to� down here...oh, the no cache. I don't actually care about the "no cache." What I want to do is send you back to the home page. So, you know what? Let's not send 200. Let's send a 301. So, we'll set the cookie to be "token cookie." And the location is going to be the home page. And we always have to send back [Indiscernible] we'll do that. This going to work? You going to do what I want? Let's try this. See if we can get it the first time. Did I actually� yes, I did actually save that. So, let's Netlify Dev. Have a Cookie Monster. That is a good idea. [Laughter]. All right. So, we've got our home page. Now, what should happen is, in here� if I did this correctly� we should see cookies for this page, which these are all different things, so let's clear all those. So, what we should see is when I log in, because we've already authorized with Twitch, it should just create that token for us. "Import.cookie.default" is not a function. Crap! Is it not compatible with CJS stuff? Hmmmm...let's see if there's, like, an ESMcompatible version. Cookie, require cookie. Using it with ESM? There's a pattern for this. Um...does the token cookie need to be serialized and stringified? It shouldn't because that's what cookie is supposed to do. So, let's try� let me see if that works. Sometimes you can get it to cooperate. That didn't immediately fail. Cookie is not a function. Dammit!
All right. Let's do a little bit� I promise, I'm not going to take very long with this. But I just want to peek at what the source code looks like. It should export, default...there is no default. Wait! Tony, were you saying that just didn't use this properly. Cookie.serialize. So, you're right. I just did it wrong. Sorry. Yes, you were correct. Um...cookie.serialize. So, let's undo that. What is up, everybody? Thank you for joining. We're playing with� well, we were playing with OAuth. We got that part to work and now we're playing with tokens. So let's see if we can get it to run. We're logging in with Twitch. Log in. There's our token. And so, this is now a working token. Now this is only HTTPonly accessible, so let's look at that a little bit so we can see how that works. So, it's on the domain, it's HTTPonly�
Holy buckets! Did that just work?
JASON LENGSTORF : So, it shows the expiration time. Which has a size. It says that it's HTTPonly. It says that it's secure. We could set it to "same site" because we're only going to use it for the same site. I'm not going to stress about it too much. But� yeah, because I don't know what the limitations are for each of those things, so I got to figure that out. Here's the thing that's cool, now that we got this, what we should be able to do is make a request to, like, do a thing. So, um, let's look at� here's the reference. Can I get, like, the username? Twitch API. Oohh, boy. Get user...get user. Is this the old one, though? I want the new API. Legacy API. Yeah, I don't want that one. I want the Twitch API. So...here's the reference. Maybe it'll just let me� the user� somewhere down here? Users. "Get users." Just get, like, one or more specified users, I guess. Okay. That's fine. We get one user and it sends back the data for that user. Do I need that...okay. So, here's what I think we should do. Let's� let's try checking if there's� let's just set one up that'll do this. So, let's get Twitch user. All right. And this one is going� it's going to need Fetch, so let's import Fetch from Fetch. And then, have I used JS Cookie? I have not used JS Cookie. Async function handler. And then inside here, what we can do is, we'll actually get the cookie in the event, because it's an HTTP request and to retrieve cookies...does it show� let's see if there's one that, like, "get cookie." No, not that one. Twitch or Netlify cookie in function. Hmmmm...this one looks like it might have the� the cookie stuff. So, event.cookie. No? Cookie...it gets the cookie. How are you reading it? Is there not, like, a parse cookie? No? No? No one's getting it?
So, I think what's going to happen is we're going to� I think it's just event.cookie. So, to event.parse. Let's look at this. We want to "cookie parse" and it'll be, like, event.cookies? No, cookie.parse. Event.cookies, I think is the right thing. So, let's import Cookie from Cookie. And, we'll consolelog that, to make sure that it's doing what I think it is. And for now, we'll just return a status code of 200 and a body of "okay" so that we don't get any errors along the way. So, let's try that and we'll� we'll do the fetch, here in a minute, but what I think we can do is, um� so, we won't be able to do this from JS and this is kind of by design, right? Is we want to be able to control the way that people access this for security. Um...log the entire event? I can log the entire event. Let's do it. So, we'll log the entire event and then we'll get whatever coming out of cookie.parse, assuming nothing explodes. Node Fetch is why that's exploding. And you're saying you don't like this because? Handler has already been declared. Oh. Yeah. Why did it autoimport that. That's weird. Okay. So, that should actually put us in better shape here.
And when we request it, um� I guess what we can do is, um...we'll just add another button here. And for now, we'll go to Netlify Functions, "Get Twitch User." And let's see how that goes. Here's our app. Argument string must be a string...maybe it already� maybe it preparsed it? Let's� let's just do the event and see what happens here. It says "okay." Got the event. Where are our cookies? Event.headers.cookie. Ah! Okay, so that's what I did wrong. So, we need to CookieParse event.header.cookie and then this should actually be our cookie, so let's run� noooooo! Headers? There. And now, we should see� here are cookies. It has our Learn With Jason Twitch and that is JSON stringified, so that all� okay, so now we'll have access to the cookies. So, in here, we can get LWJ Twitch and then we can get the accesstoken=lwjtwitchaccesstoken. And then we can get the user response by awaiting fetch and we need it to hit...one of these. This one. It was going to be here. But I can't get my User ID without� what is there� is there a way to get them, just by username. If I can get them by login, then why the heck would I need� uhhh. It'll show me the login, right? So, when somebody logs in, maybe it'll be in the validation? Those are the scopes. So, when we validate, show me the validation. Validation. Validating request, that's the section I wanted to look at. So, when we hit "validate," what comes back? We get their login. Okay! All right. So, we need to actually validate this request, as part of setting the token so that we can set their dang username. Okay. So, let's do that. We can make that work.
We will do the request to get the token. Once we get the token, we'll have it there, so what we can also do is get the token out of response.accesstoken. And then we can get the login� I guess we would� we would just get, like� validated response would be await, fetch and we're going to hit this URL here. And send in� it's going to be a method of "get." And we're going to send in the headers, including authorization, their token. Make that template. Okay. Then, we'll await that. Okay. And so out of our validated response, we should be able to� let's� the cookie body, then, I want to be� let's put in the response, but then I also want to put in the login, which will be "validatedresponse.login." And then we'll� we'll set this cookie body, instead. And so, having done that� does it include the User ID, as well? Let's also include the User ID. And that way, we'll be able to make some requests like that, that we'll need. So, let's doublecheck that that actually works by going out to here. And we're going to log in and it did what we expected. Let's look at this� this value, here. And it includes the log in on J Lengstorf. In our "get Twitch user," we need the access token. Let's see, how are we going to do this? Let's do it as� so, the User ID that we need is going to be...the...uhhh...that's fine. We'll go lwjtwitch.userid. And that's going to be method "get." And we'll send in headers. And we need an authorization header. And that will be [Indiscernible] and the access token. Okay. So, assuming that did what we want, what happens next is we should be� we need to await that. So, ".then." Response and response.json. We should be able to JSONstringify what comes back and that will include [Indiscernible] so now we should be able to get our Twitch user by clicking this button here. Unauthorized. Invalid OAuth token.
Log in with Twitch. Load. So, what do you think is missing? Let's find out. I'm going to consolelog LWJ Twitch and access token. So, those should both be there� oh, now the token is missing. Oh, my goodness! So, the reason it's missing is we didn't� [Indiscernible] object=jsonparselwjtwitch because it's stringified. So then, we can get the token object and token object and take the LWJ Twitch. We can stop this console log. Client ID is still missing. Why do I need a Client ID? Where was it? I did not need a Client ID to run� Twitch Client ID. Try it again. Now it's over here. There it was. Okay. So, what's the deal with bearer? Yeah! So, what's the deal with bearer is when you have a token like that, I don't know why� I don't actually know why the terminology came around, but what I� what I understand is that the authorization header is something that kind of standardized, over time, as people were trying things out. We saw JSON web tokens and with OAuth, this is OAuth 2. The bearer token, it differentiates the way that the token is coming in, if you're using multiple flows. By saying it's a bearer token, I believe you're indicating that this is produced by OAuth 2. I am saying all of that based on deduction and not facts. You should probably look that up. There's a high, 60% chance, that I just made that up.
Okay. So, now, we're getting our username and so the next thing that I want to figure out is, I'm pretty sure that this won't work if I try to do this as a, um� I'm almost positive I can't do this async. So let's try it and when it breaks, we'll do something else. So, I'm going to import "use effect from Preact Hooks." And then we're going to set up� I need Use State, as well, I guess. Use State. So, we'll do "const user" and "set user." Use State and we'll set it to false, as first and then do a "use" affect. As soon as we load the site� what I'm going to attempt to do is� oh. How do we want to do this? I'm going to just declare the function in here, because it runs once and I don't really care, so, async function "load user" is going to...response. Await. Fetch. And we're going to go hit Netlify Functions, Get Twitch User and we don't need to send anything with that. So, we'll wait on that res. JSON. And I saw, like, tony, you mentioned handling errors. I'm definitely not handling errors. This is quick and dirty because we got about 20 minutes left on the stream. I'd love to deploy this and let's see if we can get anybody in. Wait a minute! We could do this a totally different way.
So, let's skip this part and I have an idea. So, what we're going to do, instead of this, is I'm going to� when we do our Twitch OAuth, we get the cookie body and data. But what I want to do, depending on whether or not we get a token� so, we get this token, I want to check your subscription right in line. So, let's do this, I am� I should probably split these out into functions, but let's just do it here for now. And we're going to say "subresponse=awaitfetch." And we're going to hit this URL and we're sending it as a "get." I'm now the broadcaster and you will be the user, so, when you go in here� and this is probably going to get weird because I'm going to have to figure out� I'll probably have to, like, change the coding up a little bit so that I can test this against myself. We've got the token. But I need the validatedresponse.userid. And then we're going to send method "get." And we need headers and those headers are going to be the authorization� actually, I can just copy this right out of our [Indiscernible] so let's get this one. And then, we got to close that up. Nope. Like that. Then, [Indiscernible] res. Res.json. Okay. And, what I should be able to check� let's just log it for now. Consolelog, sub response. Okay. So, let's give that a shot and see what happens.
I'm going to go back home. We're going to log in� access token is not defined. Okay, so, I screwed something up there. Access token wouldn't be defined because we pulled it out "token." That's why you name things consistently. Try that one more time...okay. So, we got back and what why should see, down here, is "sub response data object." Dang it! Let's get our sub response data. Let's stringify this. Jsonstringifysubresponse.data and we'll go "sub 2." What? You're a liar! I screwed something up. I'm missing a� again. [Indiscernible] and we get� oh, apparently you do automatically sub to yourself. It says I'm subbed. So now what I should be able to do is I should be able to check tier. So, if tier is set...so, we're going to get constsubdata=subresponse.data. Right. And then if there is no subdata.tier, then what I'm going to do is return status code of 301 and the location� headers, location. Okay. I don't actually have another page to send anybody to, so instead, I'm just going to give an error. Unauthorized. Because I don't exactly remember how� how everything works here. If you get here, we're going to send you back to the home page and� really, we should� we should put, like, subscribers should be where you get sent. And, if you get to "subscribers," I guess I can just create this as a page, right? I'll just� new file. We'll call it "subscribers.html." And...do it like that. And then, we can set, um...okay. We do it like that.
All right. So, this� this, theoreticallyspeaking, says I'm unauthorized. Now, why does it say I'm unauthorized? Oh, it's an array. Okay. So, let's get that array data back. We will just get the first thing. So, let's go back here. Refresh the page. Log in. We go to "subscribers." Oh, everything routes to "subscribers." What? I don't get that. I don't understand how that works. Main JSX. App. App. How does the heck does routing work here? Vite Config. Does anybody understand this magic? There's no redirects. I don't get how that works. Hmm. Okay. So, Vite does some magic that I've never seen before, so we'll have to look at that. I shouldn't need something like React Router. This is what I'm confused by. Because this page, that shouldn't work. Like, why� why does that redirect to the home page? That doesn't make any sense. I don't understand how they're doing that routing. So...something is a little bit weird, not 100% sure what it is. I'm potentially holding it wrong. I would say that's the highest likelihood thing.
You know� oh, you know what I can do instead? Let's create a new folder. And we'll call that folder "subscribers." And then I'll put subscriber inside of that and rename it to "index." And then, the redirect can go to the sub folder and that should cause it to work. Let's see...there it is. Okay. So, that, I'm happy. So, then what I want to do is� so, let's deploy this, first and foremost. "Getaddeverything." If I open the site, it should open here. There it goes. It's� something not push? Everything uptodate? No, it's not. Oh, I didn't commit. [Laughter].
"Get commit." Twitch OAuth working. All right. So, then I can push that. That'll start a build. There it goes. And this should build really fast because Vite is really, really fast. The Vite build takes one second. Let's see if it works. Oh, I got to update my� ah, crap! So, this is something that I need to fix. We need to update these to be Twitch Subs Only, which means I need to replace it� I guess I just need to add it, because both should work. And...add. Save. Come on. Oh, it saved anyways. Okay, good. Good. So, that part worked. And then I need to go into "Get Twitch Users" and look for the local host. Oh, I guess it's only on the home page? App.jsx. That part needs to change. Nope. Push that up. Build nice and snappylike. Okay. Refresh. Try it again. Okay. So, this got closer. Why did it send the code here? Subscribers exists, though. Oh, I wonder if, um, when it builds, it doesn't pick up that Subscribers page? That's okay, I guess? We can� this is the worst! Nobody look at what I'm doing. So, postbuild, I'm going to...move...subscribers...to dist. Does that work? Because if we run NPM Run Build, right, then it should create that "dist" folder that's got the asset.index and then if I run "move our subscribers dist," okay, so what if I don't have an "r." That works. Move this back out so that it's not broken and go back into our Package JSON and now we'll do "Git Commit." Okay. Yeah. Not my� not my best work, but it should solve the problem. So, let's go there and check that out. Because I want to do one more thing and this is one that I'm� I think� let's just make sure this actually works. That's not what I wanted to refresh. I wanted to refresh this one.
Didn't work. Why didn't it work though? Postbuild...oh, it just runs Vite Build, it doesn't run� we're going to do a quick change to the deploy settings and have it run Npm Run Build so it picks up that PostBuild command. We will trigger a new deploy. This is the Starship Prompt. It is wonderful. Yeah, I can feel it, too, Ben. This is the one. This is the one that's going to work. We're going to get ourselves a published running site. Do it! Do! Oh, why didn't it work.
No, this will work, this will work. Yes!
JASON LENGSTORF : No, but it ran Vite Build. Maybe I have to push something because I built the Vite Build? I bet it's in the Netlify Toml. And that overrides. You got to fix it there. Let's "get commit" fix. Use the right build command. Hit "push." I'm glad these builds only take, like, 15 seconds. It's good over here. Try it one more time. This is going to be it. Let's believe. Make sure it runs the right command. Come on. Come on. Come on.
It would be neat if this just worked.
JASON LENGSTORF : Yeah! Okay. All right. We did it! And if we look in the application cookies, we'll see that we've got a cookie set, here, and that means that everybody now� first try, absolutely!
[Laughter]. Everybody should now be able to go to this app and log in with your Twitch and it should redirect you to the subscribers page, if you are� if you're a sub. And it should show 401 unauthorized if you're not. Here's the catch, right now, if you navigate to this subscribers, it should� it'll let you in. Like, it's not actually going to keep you from doing anything. So what I want to do is I'm going to try cookiebased redirects. I've never actually tried these before. Fingers crossed that I don't get embarrassed here. But, I think we can check on the presence of a cookie. Redirect users� I want to just check the presence of a cookie. So, I think what I can do, in here, is I can go into this redirects� is there a static� does Vite have a static folder? Because that's what I really need. Um...public. Directly to service plain static assets. They are served at slash during� that's actually what I need. And by default, it's public. So, I overcomplicated this. I should have just said "a public directory." And moved subscribers into it. And then I can create redirects here, as well, and then I can go into my Package JSON and remove that hackie bit, because we don't actually need to do that anymore. And then, in my redirects, what I want to do is I want to say "subscribers, and any subdirectory of subscriber." Let's just start with "subscribers." You go to "home." Or request to legacy if the browser sends a cookie named "is legacy" or "other." Wait, can I check if it's not there? I want someone to not be able to access. I don't want "role." I just want...I feel like there should be a way for me to do this because what I really want is if you go to the home page, and you don't have this cookie, I want it to be bounce you out.
Cookiebased redirects...oh, wait, I think I can just do, like, subscribers� okay. This is what I can do because it matches in order. So, what I can do is I can just do a stack and say, "we'll send a 200 if the cookie is LWJ Twitch," because that's the name we use, right? Here? LWJ Twitch. If the cookie is LWJ Twitch. Otherwise, if it's not, we'll send them to the home page with a 301. Okay. So, that should do it, I think. So, let's� not even test it, let's just try it. Let's add everything and we'll "get commit." All right. So, now what we should see is once this deploys� oh, we're out of time. We're so out of time. All right, everybody, this is the moment of truth. If this doesn't work, I have to end the stream because I'm out of time. But, site is live. Subs only. I can go to it. We delete the cookie. Hahahahahahahaha! Yes! In the nick of time. All right, everybody, with that, we're going to� we're going to do a shoutout. We've got White Coat Captioning on the stream, as always, making the show more accessible. That is through the generous support of Netlify, Fauna, Hasura, Auth0. While you're checking things out, look at the schedule. I'm not going to take any time to show who's coming up. Get in here, we've got amazing things coming up.
With that, this has been a ton of fun. Chat, thanks, as always, for keeping me company. I am overthemoon that we were able to get that to work. Let's go find somebody to raid. Thanks so much for hanging out and we will see you next time.