March 17th, 2021 × #Authentication#Security#Web Development
How To Build Your Own Auth
Scott and Wes discuss implementing authentication from scratch using sessions, JWT, cookies, CORS, CSRF and related concepts.
- Scott and Wes introduce the podcast episode on building your own authentication using JWT and sessions
- Scott explains how he recently implemented a custom authentication system for his platform after trying other packages
- Scott discusses pros and cons of custom auth vs using a package
- Wes talks about his experience implementing auth in his courses
- Scott explains what JWTs are - encoded but not encrypted tokens containing JSON data
- Wes explains the 3 parts of a JWT - header, payload, and signature
- Wes explains common ways to use JWTs - cookies or local storage
- Scott explains the difference between encryption, hashing and salting
- Ad for Prismic, a headless CMS
- Discussion on user registration and storing salted/hashed passwords
- Discussion on using HTTP-only secure cookies for JWT storage
- Discussion on using Caddy for local HTTPS development
- Explanation of CORS and implementing it
- Ad for Hasura, instant GraphQL API service
- More on CORS implementation
- Explanation of CSRF and CSRF tokens
- Explanation of user authentication process
- Wes mentions packages like NextAuth and Passport for auth
- Scott's sick pick - Remarkable 2 e-ink tablet
- Wes's sick pick - Operation Odessa movie on Netflix
- Scott plugs his authentication course
- Wes plugs his Advanced React course
Transcript
Announcer
You're listening to Syntax, the podcast with the tastiest web development treats out there. Strap yourself in and get ready. Here is Scott Talinski and Wes Bos. Welcome to Syntax. This is the podcast with the tastiest web development treats out there. Today, we've got rid A really great show for you on building your own authentication. So diving deep into JWT and sessions and tokens and rid Cookies and local storage and CSRF and all that stuff. We're gonna attempt to explain how it all works.
Scott and Wes introduce the podcast episode on building your own authentication using JWT and sessions
Announcer
With me as always is mister Scott Talinsky. How are you doing today? Hey. I'm doing okay.
Scott Tolinski
Rid It's a needle needling done in my back this morning, and that feels pretty good. So I'm just, trying to trying to wake up here.
Scott Tolinski
I I was telling Wes before we recorded, we we had, like, what, a, like, a 4 course, like, wine pairing with our Valentine's Day meal that we had order we had done carryout from one of these fancy places, and they let you bring it all home, including the wine pairings. Yeah. So ready. I'm officially dragging. I think it's safe to say definitely dragging today.
Announcer
Yeah. Yeah. You gotta be careful with that once you're once you're over 30. You of those. Casually have 3 glasses of wine on a Sunday
Scott Tolinski
night. No. Thank you. Yeah.
Scott Tolinski
It was a it was a it was a decision that we made. It was, like, after the re 2nd course, we're like, should we continue with this? I guess so.
Scott Tolinski
And then the kids got to bed somehow, and and we just passed Out.
Announcer
Oh, man. We have 3 awesome sponsors today. Prismic, Headless CMS, second one, LogRocket JavaScript session replay, and third one, Sura, which is the instant GraphQL API for your data. We'll talk about all of them partway through the episode,
Scott explains how he recently implemented a custom authentication system for his platform after trying other packages
Scott Tolinski
but Let's get on into it. So you recently did redid the auth for your platform. Right? Yeah. A little bit of the history of the auth for our setup was that we had rid. Originally just used Meteor, which has baked in auth. Right? And it's all baked into the platform. So I it's something I've never had to worry about. Even In the initial phase of things, even the UI was based in the platform. It was all just a drop in. Right? So, for me, it's been largely one of those things I've rid unable to ignore for the most part. Now at some point, we were like, okay. We need to get off Meteor because we're only using a couple things from it, the build tool and the auth system. Right? Build tools are a little bit easier for me to wrap my head around. What do I need to do? The auth system, I was like, okay. 1st, let's go ahead and try to replace it with Another package. So we found accounts. Js, which is made by some folks who used Meteor in the past. So it was very compatible. It's Graph based. I had to do less work apparently to connect our current account system. Like, the last thing I want is all of the users to have to Resubmit their authentication or something like that. You know, like, log everybody out and whatever. So I was trying to do this as, like, low friction as possible, And we wrote accounts JS for a little bit, but we kept on hitting kinda little weird issues here and there. And what happens with systems, especially re One's baked in accounts. When you try to do a lot in an accounts. Js, it's trying to do a lot. Right? You can use any database with it. You can use Any API layer, REST, or GraphQL.
Scott Tolinski
You can use Mongoose or MongoD, like, whatever. They're trying to do so many things. And because of that, rid The exact qualifications of the things that I need are very of, like, 10% of what that package does.
Scott discusses pros and cons of custom auth vs using a package
Scott Tolinski
So at some point, we looked into it and we're like, would it be worth it for us to just roll our own a 100% from scratch? That way, we would have full control over the verification process, the full control over user accounts in general, and stop relying on any sort of package.
Scott Tolinski
And I went for it. And then she said, you know what? I'm gonna learn everything there is, to learn about this stuff and do it the best I can and just start Hacking away on it. Next thing you know, I got ourselves an account system that's fully customized and is completely feature compatible with what we had. Now is that the smartest thing moving forward? Maybe. We're gonna find out.
Scott Tolinski
But we we run our own account system, and it wasn't so bad. So, now my head is ultra in this Space. Awesome. Yeah. I've I've done my fair share of
Announcer
login systems over the years, my own personal course platform, couple different tutorials.
Wes talks about his experience implementing auth in his courses
Announcer
The last time I recorded Advance React, we did it all ourself.
Announcer
And then the time that I rerecorded, I used the the built in rid. 1 in in Keystone just because it's there. And it's it's definitely tricky, especially, like, if you're not all that interested in going down the rabbit hole and, like, you really have to learn rid A lot because you could screw it up.
Announcer
And, that could be a huge security hole in your application. So you need to make sure that you know what you're doing. Rid But I feel like I've done it enough now that I know the ins and outs of all of it. And and today, we're gonna be primarily focusing on rid One type which is, like, JSON Web Tokens, which is, I would say, probably the most popular way to do auth these days. Yeah. And so for a bit of a heads up, rid. JSON Web Tokens, JWT
Scott Tolinski
as you often see it. JSON Web Tokens. Let's give a little bit of a a background on what these things are. Rid I think one of the biggest misconceptions for people who don't know what they are might think that it is encrypted data, but it's not encrypted. It's encoded. It's just base 64 encoded.
Scott explains what JWTs are - encoded but not encrypted tokens containing JSON data
Scott Tolinski
So, like, if you've ever worked with images where you've encoded an image into a string of data.
Scott Tolinski
That is just a string of data that's readable by the computer, and it outputs some information. So rid JWTs are not necessarily secure. You know, I kinda wanna put something secure inside of a JWT that the client can rid Access, so to say. But, obviously, working with authentication, we wanna be putting in information that can log the session in. So JWT rid As a base 64 encoded but not encrypted token that contains JSON data, essentially.
Announcer
The way that it works is that when somebody signs into your application on the server side, you take their username and their password or you take ready. Their whatever method they're using to sign in, we'll use username and password as the most common way to do it. You take that and you make sure that those the username and the rid are are correct. And if they are, we'll talk about matching if they're correct in just a second. But if they are, then you generate what's called the JWT JSON Web Token. And rid This token has 3 parts to it. It has a header, it has a payload, and has a signature. So the header is just a little piece of information about rid. What type of algorithm was used to generate this JSON Web Token? So there's there's all kinds of different ones. I don't understand what they are. I just rid. So sort of pick the most popular one and and go with that.
Wes explains the 3 parts of a JWT - header, payload, and signature
Announcer
The payload is probably the more important part. So the payload will include JSON.
Announcer
It it includes JSON in your web token, and that can be used to hold information about the user. So Most popular is gonna be the user's name, their email address, like a a refresh token. We'll talk about what that is in just a second. But, Sometimes if you wanna hold a little bit of information about the user, usually 3 or 4 or 5 different pieces of key value stores, rid. You can stick that in the the JSON Web Token payload.
Announcer
And the reason why that's so handy is because when somebody makes a request So let let's say somebody requests, forward slash courses. Then what you can do is you can quickly look up the users. Re You can take their ID, and then I quickly look up courses where that user's ID is equal to it. So previously with ready. Session based, you would have to look up the user in the database every single time there is a request, and that still is a pretty popular thing if you need to do permissions and whatnot. But Sometimes if you just need to know the user's name or their ID or any of this information, it's not necessary to go to the database and look the user up rid Because that data comes along in the payload. Now you might be saying, okay, Wes. It's not like a security issue if the user's, like, email address is in the token.
Announcer
Like, couldn't the user just take that out of the token, change the email address, and put it back in the token? And that's where the 3rd part comes in, which is a it's a signature. Rid. The signature is the bit where you take the the header and the payload, and you run those through the actual algorithm.
Announcer
And if somebody changes their user ID or email or anything in that, then that signature is not going to be rid valid against the data that it's being sent, and your back end will say, uh-uh. Something broke here because you tried it. And, like, I was doing this myself on Friday. I was implementing auth in rid. One of my apps, and I was like, okay.
Announcer
I had the most basic auth, which is just check if the user's email address is my email address.
Announcer
Rid. And I was like, well, could somebody just, like, change their email address in the JSON Web Token? And I tried it, and the answer is no because then the data rid Does not match up with the signature that we have there. So those are the 3 parts of JSON Web Tokens. Yeah. And so how we ended up doing it is we did
Scott Tolinski
based flow of things where the session is tracked in the server, and we have 2 JSON web tokens that get set, and they get set in cookies. We'll talk more about that in a minute, But we set an access token and a refresh token.
Wes explains common ways to use JWTs - cookies or local storage
Scott Tolinski
And so the access token contains only the session ID read. And the user ID, and it expires whenever the user closes that browser window.
Scott Tolinski
Then there's also the refresh token, which rid. Basically, just includes the session ID, and that session ID can look up whether this is an active session and check to make sure rid that the the user is correct, so that way it can generate a new access token and send the user a new access token. This is also something that works if you're, like, Opening up in a a new new browser window. Although, I think session cookies actually stay when you open up a new browser window. So I guess it's just If the the browser window's closed, then that session cookie, the access token,
Announcer
JWT is then reset. Rid So what's the benefit of doing that sort of, like, 2 step process over just giving someone an access token and accepting it for rid forever or however long you need it. Yeah. It prevents, like, stale access.
Scott Tolinski
So the refresh token will not authenticate a session without checking in rid to the database without checking in to make sure that it's an active session, where the access token itself does not check for an active session. It just Grabs the user information. Do they have an access token? Then the current session is valid. Therefore, we don't have to do the whole rigmarole of checking to see if their session is active. This allows us to be able to, you know, pull withdraw sessions from people, those types of things, and it also has an expiry of, like, 90 minutes. So that way, if it 90 minutes past that token will become expired. The access token, it will need to use the refresh token. But if we then cancel that session, rid. They'll have to log in again themselves. So what it does is it gives us its administrators much more control over who's logged in, when and where and how rid Via active sessions.
Announcer
Yeah. If you want to log somebody out,
Scott Tolinski
you can do that. Right? Like, that sometimes that happens, or someone will say, re Hey. I logged in to this computer at school, and I forgot to log out, and now I'm now I'm gone. Can you How do I sign out of other things? And that's what you can do with those tokens. That's also how you would give the user that interface as well. You know, sometimes you see in your apps, they'll say, you currently have, rid You know, 5 active sessions. Would you like to remove any of these active sessions or whatever? You know, have you ever seen that interface? So that's typically how they would do that. Again, that's a database
Announcer
Table of sessions itself. So we have a a full on table that is just sessions. Beautiful. Yeah. I I do the exact same thing with with my course platform as well. Rid. So if you wanna be able to log somebody out, you just gotta nuke their specific session
Scott Tolinski
or all of the sessions associated with that account, and then they will no longer be able to be re Logged in to the course. Yeah. Okay. So let's get into the the flow. So that's a JWT. Right? That's where the access and refresh token or perhaps User information to start. Maybe we should take a step back here and say, alright. Registration.
Scott Tolinski
When the user submits their username and password to your site On registration, here's what happens. K. So the username and password are typically sent in the plain text over to the server. Rid Now you might be wondering, like, is that a security issue? Well, that's why we have SSL certificates. An SSL certificate is going to encrypt rid That that information on the go away from the server or from the client to the server so that plain text username and password are not found. Now some people rid Still prefer to encrypt the password before sending it along, but it's not really required.
Scott Tolinski
Rid. It doesn't even make sense to me as to why you would do that except for maybe server logs or something like that if you were logging people's passwords for any particular reason, which you shouldn't be doing.
Announcer
Re If you're in encoding it on the client, then the key that you're using it to encode it would be accessible to The person. Right? Yep. It doesn't make sense. I I got an email like this one day. Like, I often I'll get these emails from people who've been like, I found a major security issue in your platform. And then I'm like, oh my gosh. And then you immediately jump on a call with them, and then they're like, you're sending your passwords in plain text. I'm like, Alright. And then I have to have, like, this, like, 10 minute ex like, I don't fault the person for not understanding how it works, but it's Kinda like my blood's pumping and everything like that, and it's kinda scary. Right? So, like, let's just make that very clear. When you log in to a service, re You send your password as plain text. Even though there's dots in the input, it's just plain text being sent over to your server. And the reason why that's okay rid is because the connection between your browser and the server is encrypted, meaning that the NSA, somebody else on your same Wi Fi, rid The US government, those people cannot sniff the wire along the way. Well, they can, but they can't unencrypt rid The data that's going along the wire at that time. There's some debate as to whether that's true or not, but that's the way SSL works. And before we even go any further, because the next part's gonna include 2 more dictionary words here, I think maybe a good explainer between
Scott Tolinski
Encryption hashing insulting.
Scott explains the difference between encryption, hashing and salting
Scott Tolinski
And so if if if you've heard those 3 words before, you might have conflated them in some sort of way or maybe known that they're different.
Scott Tolinski
But here's the difference. Okay. Encryption is the use of a key, like a secret, to obfuscate and retrieve data. You have some data. It encrypts it with a key. It cannot be cracked without that key.
Scott Tolinski
However, you can get the information, encrypt it, and get it back, that's what makes it encryption.
Scott Tolinski
K? Rid Hashing is the same type of deal, but it's a one way trip. You cannot unencrypt a hash. A hash rid It's obfuscated data that will permanently remain obfuscated.
Scott Tolinski
So you could think of it as a one way encryption.
Scott Tolinski
So that's hashing. The most popular hashing algorithm is probably SHA 256, which is what we use. SHA stands for rid Security hashing algorithm. So you've probably seen sh a 256, m d 4, m d 5, that type of thing around. Sh a 256 is is probably the most common one at this point. Now there's also salting, which you probably heard. Salting hash your passwords. Do not store plain text passwords.
Scott Tolinski
So what salting does is it adds a predetermined amount of text rid The end of your data. So if you had a password and let's say the password is password and your salt let's say the salt is salt. The new password would be password salt, all one word, then you would hash that first. Now the thing is is that the server needs to know what the salt And the hashing algorithm and all of that is so that it can properly compare them later.
Scott Tolinski
So that is the difference between salting, hashing, and encrypting. So what happens when we send our plain text passwords to the server, you're creating a user. Typically, what you do is you salt The user password, then you hash it, then you store it in the database. You do not store a plain text password in the database ever. Because if your database is compromised, then those plaintext passwords are gonna be for the picking, and then anybody can potentially get access to that information.
Scott Tolinski
Now remember what we said about hashing. Hashing is a one way trip.
Scott Tolinski
So unless the hacker has an extremely powerful computer, They know your salt. They know your key. Any of that stuff, it is going to be extremely impossible for them to unencrypt rid That hash, it just is not gonna work. So that's why we salt and hash. Yeah. The way that unencrypting
Announcer
or or unhashing passwords works rid It's not that they're just, like, running this algorithm. They're just running random passwords through the hashing algorithm rid And seeing if they match it. So if sometimes you hear probably the most common one is is when a WordPress database gets leaked. Re WordPress, I don't believe it uses salting. It just uses MD 5 straightaway. And if your password rid is dogs, and you run it through MD 5. The output of here, let me let me do it for you right now.
Announcer
The output of dogs from MD 5 is d two eight, d two d, and blah blah blah. It's like rid. This is a really long string. Right? And then if someone were to find in a database this d two eight d three Hash, then they will know. Okay. Well, that that's a very common word. Someone's already hashed that, and there's there are massive databases online of re Already hashed words. Like, people have been running computers day in, day out for 20 years to make these huge databases. So rid That's why it's important to salt your passwords because as soon as you add some random piece of salt to your hash, then these predetermined rid. MD 5 hashes are no longer good to them, and they will have to first, they would have to know your salt. And then second, they would have to start generating rid Their own database of of hashes, and that's significantly harder for somebody to do. So that's a that's a good thing for you, the application developer. Always salt it. Rid. And then it's also good for just like people who are setting passwords is don't use common dictionary words because they're gonna be yeah. They will be guessed. Rid Yeah. They'll frequently be hearing this as something called
Scott Tolinski
brute forcing, which is just the sheer amount of rid Peter force being just thrown at this thing trying to trying to try every possible combination.
Scott Tolinski
But if they don't know the salt, That's not gonna work because, again, the hashing is a one way street.
Announcer
And before we get into the next section, let's go ahead and talk about one of rid Sponsors today, that is Prismic. Yes. Prismic is headless CMS. The way that it works is you sign up, you create your data types, you create relationships, and rid They just generate an interface for you. You don't have to it's all hosted for you. You don't have to code anything yourself. The whole UI is just ready. Created for you. There's a REST API. There's a GraphQL API. You can pull it in. One thing I'm gonna talk about today is rid A couple of their features, they have scheduling and previews for their data. So if you wanna be able to rid. Modify some data and then preview it in your app. You can do that. Or if you wanna modify some data and say, alright. I want this to go live, like, the price of an item. I want it to go on sale ready. Monday at 9 AM, you can go ahead and schedule that. And that's really, really cool because this is not just a very basic bare bones rid CMS, there's a lot of UI that your end users are going to love to use. So check it out. Prismic.i0forward/ syntax. Thanks so much ready. Prismic for sponsoring.
Ad for Prismic, a headless CMS
Announcer
Alright. Let's talk about the next piece.
Scott Tolinski
So the user has re Signed up for your site. And by signing up, what they've done is they've created a unique salted hash stored in your database that is one way re Encrypted a k a hashed, not encrypted. I keep saying one way encrypted, but encrypted by definition, I believe, means that it's two way. Rid Sorry about that. Don't wanna get blamed about that. So the data is stored in the database, and how do we log somebody in because the registration process and the authentication process are 2 different things, and I think often we conflate this as being the same same deal.
Discussion on user registration and storing salted/hashed passwords
Scott Tolinski
Well, how we chose to do the authentication piece, it would be to automatically log the user user in after registering because that Kinda makes sense. Right? But for us to log a user in, what we did is we stored the access rid token and the refresh token, which again are JWTs, we stored those as HTTP only cookies, and those are set Server side. Now JWTs could be stored anywhere, and primarily, you'll see them stored in cookies or local storage.
Scott Tolinski
Now some people misunderstand this cookie storage as being the same type of cookie that you can store and set from anywhere, and some people do do that. But The thing to know about normal cookies and local storage is that they are both accessible by anything on the client, Browser extensions, you name it. Right? If it's on the client, it has access to it, except for HTTP only cookies, which can only be set or accessed server side, which is kind of an interesting thing that we I've largely forgot about now that we've been doing so many things client side. Right? So for us, the cookie is set via the response payload on the server, rid. And it is only accessed on the server, so the client doesn't really do anything except for store it. Now we also use the secure flag for HTTP only cookies, which is only,
Announcer
accessible if you're running on an SSL certificate, same domain sort of stuff. So That's important. We should point out why that's important is because if you're using HTTP, not HTTPS, With your cookie, then your cookie value is going over the network. And I remember when I was in high school there I've talked about this before. They're or not in high school. In university, there was this rid Firefox extension called Firesheep.
Announcer
And, like, you could just run it and sit in, like, a coffee shop in people's Facebook and whatever rid. Would just start popping up, and you just click the button and it logged you right in. I'm sorry. I'm told that's how it worked. It's unreal that, like, you should never ever send a cookie value over ready. HTTP.
Announcer
And unfortunately, that's pretty common with, WordPress right now just because most people install a WordPress and they don't have an SSL certificate ready for it. And then you're sending that cookie over the wire, and that could be very easily picked up whether it's on public Wi Fi or Anyone along your ISP, any anyone along the the line can grab it. Mhmm.
Scott Tolinski
That is a very important piece, I think. Rid I remember listening to some podcast when I first found out about, like, the what people do for, like, packet sniffing and stuff where they'll jump on various networks on rid Airport Wi Fi and just harvest data.
Scott Tolinski
This is big. Yeah. Truly horrified.
Scott Tolinski
So that's always something to keep in mind. There's this read. Tweet thread that went around the, couple months ago, and this guy's like, you do never need to use cookies For anything, can we all just agree to not use cookies anymore? It's like, no. You're wrong. Cookies, HTTP only.
Scott Tolinski
Secure cookies are the way to do authentication in my mind, and the only way I saw that was more secure than that was rid. Hasura is saying to store it in RAM. So, like, Hasura had a blog post about how to store it in RAM, and it was so Complex. I was like, oh, man. I don't know about this. So I checked I checked some other sites like Reddit and saw how they were doing it. HTTP only cookies. Okay, people? Now one thing to know about these is that you might be thinking, oh, yeah. I'm using cookies. Do I need to toss-up one of those ugly GDPR banners now? No. Actually, I found out that GDPR cookies do not apply to any required cookies, things like authentication.
Discussion on using HTTP-only secure cookies for JWT storage
Scott Tolinski
So as long as it's not tracking people or whatever, it's just used for authentication, your access tokens, you don't need to tell them that you're storing rid Those are subject to GDPR. Yeah. I just confirmed that too because I I remember hearing that, and then I wanted to confirm it before we record the show. And let me even see if I could find out the exact wording on there because GDPR is something that is interesting, But I can't necessarily say I'm an expert on it. Okay. So the European Commission describes strictly necessary cookies as user input cookies For the duration of a session, authentication cookies for the duration of a session, which is what we're doing, and then user centric security cookies, multimedia Content player session cookies, load balancing session cookies, and user interface customization cookies rid that are set for a few hours. Those are all GDPR compliant. Okay? So you don't have to worry about it. It's mostly just the tracking cookies Yeah. That you have to warn about. Like, I don't know GitHub
Announcer
just went all out and said, alright. Well, we're just not gonna have any of that on our website. Yeah. We don't have any of that. Ready. They're able to to get around having to do that.
Announcer
So the other way to do to where do you put your JWT, you put that in you can put it in a local store. So ready. I wish to say, like, we're talking about the web browser here. But, like, if you're building, like, an iOS app in Swift, you can still use JWT.
Announcer
You just have to, like, store that token somewhere. Because as far as I know, iOS doesn't have a concept of of cookies. That's that's unique to act an actual browser. So, re you can also store them in the client.
Announcer
A lot of people will store them in local storage.
Announcer
And when you do it in local storage, you are now Having to handle it all client side. So when the JWT comes back from your server, you signed in successfully. Rid It's up to you to put that thing in the local storage. And then every single time that you make a request, it's up up to you to pull that thing out of local storage and attach to your fetch requests or or whatever you're using to actually send the request. And this was, like, really popular a couple years ago, and I I sorta rid Feel like the tides are turning back to cookies, thankfully, just because I found it. A, I just found it to be a pain in the butt to have to pull the cookie out ready. Every single time where you had to write some middleware that would automatically do that for you. And then also the initial request rid. When somebody types in a page and goes to it, you never get the authenticated version of that page rid. Because local storage JWTs don't go along on that initial ride like a cookie would. Because, Let's say you're going to westboss.com.
Announcer
You type in westboss.com and you hit enter. What's happening there is that you're making a request to the server, And any cookies that are in your browser because you visited that website before, they're automatically sent along for the ride, Whereas local storage cookies are not. They don't come along unless you make a JavaScript request, and send it along. So that's a bit of a pain rid If you wanna do, like, server side rendered authenticated states and that's also sometimes when you you visit a website, And then, like, a second later, you see the login button change from login to your your avatar. That's because they had to load the page And then make a request to, is this user signed in? Then it had to come back, and then they said, oh, it is. They are signed in, and then they had to update their their UI. So I'm, rid. Like, there's some security blog posts of cookies being more secure.
Announcer
I don't totally understand either of them. But rid Just from, like, a ergonomic standpoint, I much prefer the the cookies
Scott Tolinski
approach. I think the the only reason that cookies are more secure for this rid are when you're using HTTP only cookies because they cannot be accessed by the client whatsoever. And so that's the only reason why they could be more secure Because if you do have a browser extension, that browser extension can pluck your JSON Web Token out of local storage if it wants.
Scott Tolinski
Rid. So that's something, but, again, I don't know how real any of these threats are actually are in practice.
Announcer
Yeah. It could. Like, you gotta stay, like, suspicious, I think when you gotta it it gotta stay suspicious. It's gotta stay. So if you go to, like, a logged in website, type in document dot cookie into your console, and you'll see rid That those are not all the cookies that are available to you because some cookies are not available via JavaScript, and that stops rid. Malicious actors like a bat like a a browser extension from reading that value. There is still is another thing called CSRF. We'll talk about that in a bit. But I actually think HTTP only cookies blocks
Scott Tolinski
CSRF, so that might be the thing. And if you're sending, like, a cookie along local storage, you would have to use Authorization header. Is that correct? Because I haven't personally used the authorization header. So since we're doing ours via cookie
Announcer
You can send them along rid any way that you want. Your just your server side has to be able to pull it out of it. So a lot of people will send them over rid Authorization header, like a bearer
Scott Tolinski
the and that like, a bearer token as an auth header is another one. But you could literally send it along as a query string if you wanted, rid But that's what auth headers are for, though. So, yeah, most people do that. Okay. Interesting. I mean, since I've been writing my own, I've only used the HTTP only cookie for it. And it has been largely easy because it's just available to you on the server no matter when you need it, and any user like, anything that we're calling is always being called from the server for us. So, I mean, that seems like a a big pro.
Scott Tolinski
Another small thing before we move on to the next section would be that re HTTP only cookies, they have very strict rules. And so because of that, I have found that they are exceedingly, PIA to get set up in specifically some browsers.
Scott Tolinski
Safari being the one that has the biggest restrictions.
Scott Tolinski
And so for us, we had to move our domain off of local host to get the HTTP only cookie Authentication to work locally.
Scott Tolinski
So we had to do a reverse proxy for our Server so that it could be, 1, h t t p s locally, which is a big thing. Right? I don't know if you've ever had any issues rid Moved it up to the server HTTPS, and maybe you missed a URL here or there that needed that HTTPS in front of it or something. So now we we actually run our local environment all re HTTPS note. We're using Kaddy, which is a Go based server for this. We use Kaddy as a reverse proxy, and it's like rid Two liner of configuration, and then we get h t t p s level up tutorials dot dev and then API dot level up tutorials dot dev rid On local host using we, you know, update your host file, whatever, so that that those requests can pass through. And I gotta say, rid Kaddy, if you're interested in running an HTTPS on your local host or a a reverse proxy where you're using a real domain for developing locally, rid Caddy, in my mind, is the way to go because just about everything else I looked into in terms of setting up some sort of a process to generate SSL certificates for local hosts just had me being very upset at life. I mean, I just I I just really did not like it. And then we we popped in rad Caddy in a couple lines of configuration, and then we ran Caddy run, and it automatically generated rid. SSL certificates, 1st try, had everything working, and just perfect. So Matt Holt, who created Caddy, He had tweeted at us when we were asking about, like, what do people use for this kind of thing? He was tweeting, why don't you use Caddy server? And then at first, I forget if it was you or me, but somebody was like, I don't wanna spend, rid You know, Minit's learning some config, and he was like, try seconds. And I was like, oh, okay.
Discussion on using Caddy for local HTTPS development
Announcer
Let me try it. And it was rid. It was literally seconds. No. I I had used it a couple times. I went back in the syntax show notes, and we I had mentioned it, like, 2 years ago, but I've never re Actually used it on a full blown project that always done NGINX configs, and I was amazed at how quickly I could get HTTPS Running. And it wasn't the, like, crappy, like, it should it is HTTPS, but you gotta click allow or continue anyways on your browser.
Announcer
Rid Like, it just, like, worked. And I was like, that's the first time I've ever seen HTTPS on localhost just work. Rid Yeah. Without a whole bunch of, like, BS to go along with it. Yeah. Totally.
Scott Tolinski
If any of this stuff scares you, well, maybe you wanna have some sort of error exception handling service rid track when users are failing their login sessions, and then one of those services that gives you a nice visual into the user's flow of things is rid LogRocket. And I'm talking about logrocket.comforward/syntax. This is one of those services that rid Ridley needs to be seen to believe if you've ever had an issue where a client has a bug on your site and you cannot reproduce it, Well, no longer. Because what this does is it takes a session replay, which is a video scrubbable video replay where you get access to the dev tools and stuff. Rid. You could access to the network network tab. You could access to the Redux store, any of that cool stuff, the error console log, to see exactly what happened at the scene of the crime. So check it out at logrocket.comforward/ syntax. Sign up right. Right the second, you'll get 14 days for free.
Scott Tolinski
Okay.
Announcer
Rid So I I thought we would dive a little bit more into sessions.
Scott Tolinski
So sessions, as we stated, are way to greater control who's logged in and from what computer.
Scott Tolinski
If you've ever logged in from a website and then, maybe you went on your phone and also logged in on your phone and then you went to the library and then logged in, Those are 3 different sessions, and those sessions are active until the user clicks the log out button. And so, again, this is taken care of by access and refresh tokens. And a refresh token, as you'll often see, is not capable of rid Authenticating the user on its own, but what it's capable of doing is looking up an active session. This means that For this cookie to be used, it must be run on the server in the database.
Scott Tolinski
Again, it's only, like, where our access token is a session based re Cookie. That cookie is removed at the end of the session. This one can stay a little bit longer, maybe like a couple days.
Scott Tolinski
However, the caveat there, you might be saying, well, how do people if they close their browser for more than a couple of days. Well, each time you generate an access token, I actually generate a new refresh token, Which means that it's, you know, 30 days or whatever from the last time that they accessed the site, not from when they first logged in. So a refresh token is stored for us in the cookie. It's a JWT.
Scott Tolinski
It's sent over to the server. Read. It's decoded.
Scott Tolinski
It is then used to look up the session because the session ID is stored within that token. The session ID is then pulled up in the database and says, is this session active? If it's active, then it can generate a new access token, send that along to the user, And has now authenticated the user.
Scott Tolinski
So for somebody to spoof that with it, if 1, they would have to steal your refresh token somehow, which is Only stored via a security only cookie. Okay. Let's say they steal that refresh token. They somehow get it set into their browser, which they can't do anyways. They could send that along theoretically, and the session would still have to be an active session for the authentication process to work. So There's there's a lot of play here, and that's often why you'll see refresh tokens being used. It's for this whole session based flow of things. Rid. Awesome. On Chrome DevTools, you can set HTTP only cookies.
Announcer
But like you said, someone would have to first get a hold of that token at some way. And if they did, then you are in trouble anyways.
Scott Tolinski
You can set HEP only from, rid Let's say Yeah. You could do it via the the Chrome dev tools. You can't do it via JavaScript. I'm in Chrome dev tools here. I'm on TweetDeck.
Scott Tolinski
Application. I'm gonna store an HTTP only cookie for tweet deck here.
Scott Tolinski
Yeah. Mine's not letting me do it. Oh, really? Like, I can store a cookie, but I can't I can't make it HTTP only and secure.
Announcer
I don't know why. I was able to edit mine rid on Friday
Scott Tolinski
via the tools, but that doesn't really matter because that's a debugging tool. And if somebody has your token, then, yeah, you're rid You're screwed. You're screwed to a point. Right? Yeah. But that that in itself is is very difficult. Right? Yeah. So these are definitely some of the things that we're dealing with Here, when you're working with security, it's important to know that your user's information is sacred and that you need to protect it at all costs.
Scott Tolinski
So if there's anything really super secure like a plain text password, that should always be hashed. Right? And, likewise, You shouldn't be storing information that could be seen as potentially dangerous or harmful on the client where it's accessible Via extensions or something like that. Right? Let's talk about Coors.
Announcer
My favorite beer. So Coors,
Explanation of CORS and implementing it
Scott Tolinski
well, I know it's probably not, but side note, we went to the Coors Brewery Tour in Golden, Colorado. I think I mentioned this before in the show. Oh. It is So cool. I you know what? It is a giants giant facility.
Scott Tolinski
I'm not a Coors beer fan, but We've done the tour, like, 3 or 4 times because it's such a cool facility. They have their own mountain water source literally in the Rocky Mountains. It's a very, very cool tour if you're ever in Golden, Colorado. So CORS stands for cross origin resource sharing. Do you wanna dive into CORS, Wes? I know you are rid A man, of course. Yeah. So if you wanna make a request from 1 domain name to another domain name, you have ready. To enable
Announcer
cores on your server side application. So if Scott or, like, level up tutorials .com, If I wanted to pull in an API of Scott's courses and display them on my website, Scott might have an API endpoint that's, rid like forward slash API forward slash courses, and I would request them from westboss.com.
Announcer
By default, that doesn't work because rid. By default, you cannot request or push data from 1 website domain to another website domain because that's a security issue. Rid. I shouldn't be able to request, a list of your bank accounts from westboss.com because you're logged in on another tab. But sometimes you want you do want to be able to share things across multiple domain names.
Announcer
So if you've got, like, just rid. Two different domain names that are the same application or if you have an API that needs to be consumed by other domain names. You can either rid Pass a list of allowed domain names as part of your response, or, like, Scott could say, alright. This list of courses can be displayed on westbaz.com, syntax.fm, Scott Dalinski.com, and level up tutorials. Right? Those are the only 4 domain names that are allowed to request data. Otherwise, Not allowed.
Announcer
We should say that that is only true on in the browser. You can do these things Server side without the course is not a thing on the server side.
Announcer
That's a bit of a pain in the butt because when you are doing authentication cross rid Domain name, it can get really tricky.
Announcer
You usually have to visit the actual website in order for a cookie to be set. So rid. All the browsers are, especially Safari, are really cracking down on being able to set cookies via things like tracking pixels and whatnot because you used to be able to do, like, image source equals level up tutorials.comforward/ rid pixel.png.
Announcer
And then what what would happen is that you visit my website. But because I put a pixel from Scott's website in mine, rid. Scott's website now knows that you visited my website because this is our it's able to set a cookie, but it's also able to look at rid The referring request. So that is still kind of a thing, but it is much more complicated now because the browsers are cracking down, thankfully, on on a lot of these trackers and spy and ads and and all that. So, the downside of that is it makes off a little bit tougher. Rid. And generally, the thing is that you actually need to be able to visit the website.
Announcer
You can't iframe it in. You can't just set a cookie. You can't rid. Send an image, something like that. You have to actually visit the website to set the cookie and then come back. And, usually, it's not a huge issue because You are either on the same domain name or you're on the same subdomain name, at least, like auth.whatever.com.
Scott Tolinski
Yeah.
Scott Tolinski
That's largely how ours has worked with subdomains.
Scott Tolinski
You know, Chorus is frustrating for a lot of people because you do have to set it on the server. And so some people, especially if they're working with an external API, might hit that and be like, oh, what do I do now? Well, you need to get your domain on one of the approved domains Somehow, whether or not you have control over that server. Right? So next little bit, maybe we should take a a a break to talk about one of our sponsors, which is Hasura, which actually makes auth fairly easy with their platform.
Scott Tolinski
So I'm so glad that Hasura is sponsoring Syntax because this is a service I've used for a little bit myself And especially for some fun projects when you just wanna get up and running with a GraphQL API. But fear not, it's not one of those rid Prototyping services. This is the real deal where you can get up and running insanely fast with a GraphQL API and a Postgres database. And for the most part, you don't even have to think too much about the entire code of things. You don't need to hand write your server endpoints or anything like that. Hasura is really, really pretty smart and really gives you the tools to build something very advanced Very quickly, it even has a granular authorization baked in with complex rules down to the individual row and column level. And this thing is very, very configurable.
Scott Tolinski
With Hasura, users typically save more than half the time they would take to build a GraphQL URL server according, to Hasura. And and that's, like, very, very evident when you start to use it, especially if you've hand rolled your own GraphQL server in the past. There's also Hasura Cloud, which is a managed service, and it gives you things like auto scaling. Right? Nobody wants to deal with scaling. I don't wanna deal with that. And if you sign up for the free tier, it only takes a moment, and you have an instant GraphQL and REST server. So head on over to hasura.info forward slash free trial and use the coupon code tryhasura, re t r y h a s u r a, and you're gonna get 3 months of Hasura Cloud for free.
Ad for Hasura, instant GraphQL API service
Scott Tolinski
Rid And this is one of these offers you're gonna have to act fast on. Only the first 100 listeners get this kind of crazy deal. So head on over to hasura, h a s u r a, dot info forward slash free trial. Use the coupon code rid Try Hasura, h a s u r a, and get 3 months of Hasura cloud for free. Very, very cool. So thank you so much for Hasura for sponsoring.
Scott Tolinski
So we're using Fastify.
More on CORS implementation
Announcer
To set Cors, we just we used a Fastify plug in for Cors. But if somebody is running, like, an express server or something like that, What what do they do when they need to turn to Cors? Right? There's a package called on npm called Cors, and it actually works on It works on Next. Js. It works on Express. I think it's based on the Connect middleware, so it just works with most platforms out there. You pass it course, and then you pass it your settings.
Announcer
So you allow either star or you can allow an array of websites or you can give it a function where it'll regex match it based on on what you want. And then the only other thing is that you explicitly like, if you're making a fetch request in your JavaScript on the client, You some or often need to explicitly set the include credentials.
Scott Tolinski
Oh, yeah. Include credentials. Man, I feel like there's so many little rid Little things that you hit over the course of this stuff, like include credentials, course errors, obviously, but these little things, even, like, getting the rid. Origin and domain correct for our HTTP only cookies
Announcer
was like is a challenge. Every little bit was a challenge. Yeah. So the the fetch, rid. When you make a fetch request on the client, there is a credentials property that you set in the options.
Announcer
You can set that to include or same origin or omit, and that will decide whether or not it should send along your cookies for the ride rid. Every single time that you you make a request.
Announcer
And by default, if you're going cross origin, they will not. I think the default is same origin, meaning that if You're requesting from 1 domain name to another domain name. It's not gonna send the the cookies along unless you explicitly say re Include.
Announcer
Next 1 we have here is CSRF, which is cross site request forgery.
Announcer
And often when you sign into something, rid. You will also generate a CSRF token.
Explanation of CSRF and CSRF tokens
Announcer
And what that is what CSRF is is when a website rid Like, westboss.com assumes that you're logged in to another website, like leveluptutorials.com.
Announcer
Rid. And, like, let's say Scott has, like, an endpoint that says delete account. If I were to ping That delete account endpoint from my website to Scott's website, like like you somebody visits my website, And I run a fetch request that pings the delete account endpoint on Scott's website. But because the users are logged in to Scott's website, rid Then it will still send the cookies along for for the ride. Right? And that is rid Potential issue because you're making a request that's valid. And because the cookies always come along for the ride, then that's a potential issue.
Announcer
So often, rid. A server will make every single request. You get a CSRF token.
Announcer
And then that token, when you make another request, has to be sent along with it. Rid. Otherwise, you could be running into some issues there. I think you said earlier that is not an issue with rid. If you're doing HTTPS, I don't think that's true because that's not a XSS attack. It's rid different than, like, someone being being able to access the JavaScript tokens. This is just like if I request rid From anywhere on the web, those cookies could come along for the ride because HTTP only cookies are still sent along on every single request. So the fact if you make them HTTP only or not doesn't have anything to do with If that cookie is sent to the server so if there is a potential that somebody is making a request from another website, rid Then you need to think about that and maybe implement CSRF tokens.
Scott Tolinski
There's also the same site cookie property.
Announcer
Oh, yeah. It's something that's a little, beyond like, this stuff goes deep. Right? Yeah. And a lot of times, you can You can get away with just knowing enough and digging in as much as Scott and I have done, but you can also get really, really deep in there also. But beyond this, there rid. There's also, like, content policy for doing security on your website as well. That's not something I I know a whole lot. I would love to have somebody on that So it digs deep into how that all works as well. So something to look into as well. Okay. So let's get into the authentication process.
Scott Tolinski
So the registration process as we mentioned, everything comes over the wire plain text is hashed, salted, stored in the database. Right? For the authentication process, it's very similar. The info is still coming over plain text, and once again, it's salted and hashed.
Scott Tolinski
Instead of being stored in the database or anything like that, you run a compare function. So in our case, we're using rid bcrypt. Js to generate salt as well as hash, but there is also a function called compare, which you basically you salt, You hash, and then you run the compare function. The compare function that takes the new password that came in, And it contains the one that came in in the database, and it simply compares them. If they're the same, it returns a Boolean. True or false? And There's not a ton of ways to fake that. Let me tell you about that because that hash is not something that really especially if it's salted, it's gonna be difficult to brute rid that. And to be able to compare those 2 and say, alright. This is correct or not, that's about as secure as we can make it right now or at least without a a second factor, right, like a 2FA or something. Re Yeah. And you can also tell bcrypt how many times you want it to do it for. So if you have a beefy server or if you wanna wait for 2 or 3 seconds, you ready. Run this thing a couple 100 times,
Explanation of user authentication process
Announcer
and that's another layer of thing. Because even if somebody knows your your salt read. And they know that you use bcrypt. And and with a specific algorithm that you use, then they also have to know how many times did you run this thing. And every single time that you run it, it gets harder
Scott Tolinski
to reverse the entire thing. So I I use bcrypt as well on my course platform, and It works great. Yeah. So okay. So that's the authentication process. And then after that authentication has come back true, what we do is we create a new session in the database. Re We generate new access and refresh tokens. We send those along the line, and we do that via sending via cookies. Now the one thing I didn't mention when I said that we set our rid Cookies in the response for our server was that the cookies do take a couple of parameters besides the fact that we're sending them HTTP only and secure.
Scott Tolinski
One of the properties is domain, and domain can cause you a ton of trouble on Safari if things are not, Let's just say not perfect.
Scott Tolinski
So for us, the cookie domain needs to be essentially the root domain for us. Let's say Our authentication was taking place on the server, which is api.leveluptutorials.com.
Scott Tolinski
The client is accessing via www.leveluptutorials.
Scott Tolinski
Rid The origin needs to be the root domain, IE level up tutorials.com.
Scott Tolinski
No subdomain for all of those to be accessible and work correctly. So that is the registration authentication and all that and more.
Scott Tolinski
There's a lot of stuff here, and I I feel very comfortable with our current auth system. I feel very confident with it. It allowed us rid to write a little bit more customization into it here and there while while staying secure. I consumed just about a mammoth amount of content while researching for ready. For ours. So, hit us up if there's anything that we left out or missed or could have elaborated more upon. We'll hit you up with one of those sick retweets.
Wes mentions packages like NextAuth and Passport for auth
Announcer
Ready. Alright. Other things real quick. Just if you want to make this a little easier, there's a couple of packages I like. Next rid Auth is something I just tried out on Friday.
Announcer
I needed to add auth to a next application, and it was so easy To get up and running, I couldn't believe it. Like, maybe, like, 20 lines of code to get access to the GitHub login, rid. And it works so easy. And I was like, this is awesome because Next does both the client side as well as it does the API routes, which is your server side.
Announcer
So it supplies you both the server side APIs and the hooks that you can use to log in and log people out. And I was like, oh, this is rid. Awesome. Like, very rarely have I run on platforms that do both. Right? Like, Meteor was probably like that where it does both, and it's so easy.
Announcer
I was really impressed at how easy it was to get up and running. Rid I personally use passport dot j s in my own website. It was, bare to get up and running, but that was, like, 5 years ago, and it's been rock Solid since then. Auth0 as a as a service, it looks like they also do, like like, roles and permissions as well, Which is pretty cool, and you can use like, sign in with all these other like, you can sign in with GitHub or sign in with your Microsoft ID. So rid As a service, I tend to shy away from using services for this type of thing just because, like, it's not another thing I wanna have to pay for and And maintain
Scott Tolinski
like you said, Scott, like, it probably took you, what, a couple days to to implement this yourself? Probably took me a week researching and and writing everything. And then On top of that, there is there's so much extra little things like sending the emails and stuff like that that you want verification of email,
Announcer
those kind of aspects. Rid Oh, yeah. Wouldn't even talk about that. Password resets, things like that. Like, magic links is another. We'll do another, like, hasty on, a couple other Things that we didn't talk about today because there's some more interesting stuff we can talk about as well. There's a lot here, and we hit it all. And, you know, I'm very happy and confident with the things turned out. So do you wanna get into
Scott Tolinski
sick picks? Yes.
Scott's sick pick - Remarkable 2 e-ink tablet
Scott Tolinski
I have a sick pick today, and it's my new favorite device. I got it for my birthday, which I don't know when this episode is airing. Let's see if my birthday will have passed. March 17th. Yeah. My birthday was 16 days ago when the time you're listening to this. I got this neat device. It's an E Ink tablet.
Scott Tolinski
I purchased it myself for my birthday because, you know, and It has a treat yourself.
Scott Tolinski
It has, like, a week long battery life. It's called the remarkable 2. The thing is paper thin, So it's absolutely tiny, thin wise, tablet wise. It's about half the width of an iPad thin thinness wise, And there's about 0 latency on the pen. The the killer feature for this thing is is that it's a sketching and note taking out our, tablet, And the latency on it is crazy good. There's a whole bunch of different pen types. There's an eraser, and it writes all in e ink and syncs rid directly to the cloud. So, like, for instance, I wanna sketch out some notes or whatever. I just write them all up here like I would any other rid Notebook. And next thing I know, it's on my phone if I need to email it to somebody, do handwriting to text. This thing is expensive. I'm gonna say that very clearly.
Scott Tolinski
It's a inexpensive single purpose device, and I have not put it down.
Scott Tolinski
Ding dong. It is one of my favorite things in the whole world right now, and I just use it nonstop for note taking, writing little notes, whatever, messages, planning. There's all sorts of templates, so you can get, like, a backdrop of a rid To do list template or something like that, you can hack it. There's a huge community on GitHub of hackers for this thing. You know what the best part about this Tablet, besides the fact that it feels just like paper to draw on, like, it feels just like paper. Yeah. The pen, unlike the Apple Pencil, Does not need to be charged. Right? No need to be charged. Like, that's why I always pick up my Apple Pencil, and I have an iPad, and I'm like, oh, they're gonna charge the pencil. Rid Like, okay.
Scott Tolinski
This thing has pressure sensitivity, and it it's very fantastic.
Scott Tolinski
So check it out. Remarkable 2. I absolutely rid Love it. And for as much as I've been shilling it, they better send me some remarkable bucks here because I I've just been posting about it nonstop. I love this thing. I'm going to sick pick, a movie, Operation
Announcer
Odessa.
Wes's sick pick - Operation Odessa movie on Netflix
Announcer
Have you seen this one? It's on Netflix. I haven't. I've Wait. Is it, there was, like, a smuggling operation? Rid Yes. With the subs? Yes. Yes. Oh, man. So, like, I love a good drug documentary or movie in this movie. Cocaine Cowboys, man. It's, like, my number one. Rid. Oh, there's so many. And I was like, I I got to the point where I'm like, I pretty much have seen every single drug or, rid Like fraud documentary out there, and I found this new one.
Announcer
And it's about these, like, guys from Miami rid that are involved with, like, the Russian mob and the Colombian cartel. And they, like, would just, like, buy, like, and make speedboats for them. And They they're just telling the story about how they tried to buy a Russian submarine for the cartel so they could run drugs in the submarine rid. For, like, $40,000,000 or $50,000,000, something insane.
Announcer
And the guys who are telling the stories are They're the actual people who did it and are out of jail now or are on the run, and they're just such good storytellers, like the most hilarious storytellers ever. So I was
Scott Tolinski
very, very pleased with it. Check it out. Operation Odessa. Yeah. It's a good one. Cool. Shameless plugs. I have a course on authentication.
Scott plugs his authentication course
Scott Tolinski
So if you found this to be interesting, our latest course, Node fundamentals authentication on level up tutorials.comforward/prosignup for the years of 25%, And you will learn how to write in Node. Js the entire process from reset your password, rid To authenticate, to register.
Scott Tolinski
And even if you aren't planning on using this as, like, your authentication, I think it's still important to understand how this stuff works, especially if you wanna be a, quote, unquote, full stack developer. Right? This is such a huge part about being a developer. Ready. It's important to understand how this stuff works. So level up tutorials at Node Fundamentals authentication course. And here's the best part was, There is no build process for the entire course, and we don't use any front end frameworks. I'm I'm slinging everything in Vanilla JS and, you know, fetch or whatever just to Just to make it as widely accessible as possible. Beautiful. I I love those kinds doing those kinds of courses because they don't go out of date immediately
Announcer
because it's just JavaScript.
Scott Tolinski
That's my angle.
Wes plugs his Advanced React course
Announcer
Yeah. Speaking of out of date or the opposite, re My advanced react .com course has been totally updated. Speaking of not being out of date, It's totally rerecorded with, the all of the latest. So it uses Keystone JS, which uses JWT tokens, re Apollo 3, React Hooks, function components for everything, all kinds of good stuff. Headless CMS. Check it out. Advancedreact.com.
Announcer
Use coupon code syntax for $10 off. Sick.
Announcer
Alright. Thanks so much for tuning in. Catch you on Monday. Peace. Peace.
Scott Tolinski
Head on over to syntax.fm for a full archive of all of our shows, and don't forget to subscribe in your player or drop a review if you like this show.