Next.js Fundamentals, v4

Auth Server Actions

Scott Moss
Superfilter AI
Next.js Fundamentals, v4

Lesson Description

The "Auth Server Actions" Lesson is part of the full, Next.js Fundamentals, v4 course featured in this preview video. Here's what you'd learn in this lesson:

Scott demonstrates using server-side functions for signing in, signing up, and signing out in Next.js. He also discusses the importance of using the `use server` directive to ensure the functions have access to the request and are treated as server-side code.

Preview
Close

Transcript from the "Auth Server Actions" Lesson

[00:00:00]
>> Scott Moss: Let's do it. So I already have some stuff in here, like the Schemas and things like that. If you'd never use Zod, it's pretty simple, you're just describing the shape and the requirements for it. And now we are just going to make our function. So the first one we want to make is going to be sign in.

[00:00:16]
Also I have this mock delay thing, you can use it or not. It just simulates the delay so you can actually. Otherwise the app's gonna move so damn fast, you're not gonna see loading states and stuff like that on local host. So you can use it or not, put in whatever time you want, it's just simulating stuff.

[00:00:32]
So let's make sign in. So I'm gonna say, exports const signin. And for all my TypeScript people who just cannot live without TypeScript, because we're gonna use this as an action on a form, as a Server Action, it's gonna always be passed the FormData. This is a type that's part of the HTML spec.

[00:01:02]
So we can use the FormData type, and we're always gonna return a Promise, which in this case I already have a type called ActionResponse. So I'm just gonna say, FormData here. It's gonna be type FormData and it's going to return a Promise of ActionResponse. There we go. So the first thing we wanna do is we want to get the data so we can abstract it.

[00:01:32]
We can do that by doing FormData.get. This is because this is a map, yeah, FormData is a map. So we can use the get method on it to get the methods that we want. We haven't made the form yet, but just assume for now that this form has two fields, one called email, one called password.

[00:01:49]
When we make the form, we'll ensure that those are the names of the fields. So we wanna get that, so let's do that. So we can say, data = some object. Email will be formData.get('email') as string. And then password would be formData.get('password') as a string, cool. Now that we have that, what we wanna do is we want to validate it.

[00:02:23]
There's a lot of ways to validate it with Zod, I'm gonna do a safe parse. This essentially won't throw an error if it fails, instead will return an object with a success or an error property. If I do a regular parse it'll just throw an error if it fails.

[00:02:38]
So let's do a safe parse, and if it's not successful, then you can't sign in. You didn't pass this form validation, so something about your email password stuff is messed up. So in this case we'll say, validationResult =, I already have the SignInSchema.safeParse. We're gonna pass in our data, and then we can say, if (!validationResult.success).

[00:03:13]
We wanna go ahead and return that ActionResponse that has error and all that stuff, so I'll say, success: false. Actually, I'll just copy that, make it faster. So we'll do that with this freak it out object load allowance, probably success does not exist. It's right there, [LAUGH] it says success.

[00:03:38]
See, this is why I use TypeScript, it's clearly wrong at this point. Function likes ending return statement and return. Cuz I haven't returned anything yet. No, I don't know, TypeScript errors, whatever, that's TypeScript.
>> Student: I think it's not liking that the function is an async either.
>> Scott Moss: Maybe that's it.

[00:04:02]
That somehow fixed that, [LAUGH] TypeScript. [LAUGH] I don't know, man. Well, I know it's freaking out now because I don't have a return statement outside of an if statement. If I do this, it'll be happier, it's like, well, you might return something undefined. That's why it's freaking out, but I haven't gotten there yet, leave me alone, stop screaming at me, cool, so.

[00:04:26]
And then the next thing is, let's find the user by the email. We haven't made this function, so let's just assume that it's made, but it's not implemented. So let's assume that it's gonna work. We'll have to go back and make this function for it to be useful.

[00:04:40]
So we'll say, user = await getUserbyEmail, and that will be already imported for you. Up here you can see right now it says it has no exported member because we haven't made that yet, so we'll go do that in a little bit. So user by email. And then if it's not a user, then yeah, we just want to say, no, tell the user that, hey, that's an invalid email or password.

[00:05:11]
Don't tell them which one, you don't wanna expose your logic, but you just say that. And then from there, we wanna verify the password from the password that they passed in with the password that exists on the user in the database. So we can say isPassword, let's type exactly what I had here, PasswordValid.

[00:05:34]
We can say await verifyPassword with (data.password, and user.password). And guess what? Same thing, if it's not valid, get out of here. Pretty much the same thing we had, so I'm just gonna copy and paste that. And again, don't say, your password is wrong, cuz now they know that that email exists in your database, yes.

[00:06:07]
>> Student: Why do we need to put the use server at the top when all of these are server components by default, right?
>> Scott Moss: So the question is, why do we need to put use server at the top if all these are server components? So none of these are components, these are all just functions, that's one thing to recognize.

[00:06:26]
And then two, if I don't put the use server here, these are just regular functions. They won't have access to the request, Next js won't make routes for these, if I don't put that directive here. They'll just be treated like a regular file that I can import and use anywhere.

[00:06:42]
And if I try to, if I don't do this and I try to import this into a React component, it's gonna break. It's 100% gonna break because there's a bunch of node specific stuff in these libraries that won't run on the browser. This right here is literally telling Next.js, hey, these functions that I'm making, can you go make routes for them and make sure that their code is bundled with the server modules and not with the client modules?

[00:07:17]
That is what this directive is telling Next js. Without that, you just have regular functions and those functions will not work in the browser. There are other ways to use the use the use server directive. This has nothing to do with Next.js, it's React. So definitely recommend checking out Brian Hodes course on it, but you could on a function level do a directive here at the top of a function.

[00:07:44]
But you typically would only do this if you were making the function in a component. If you're making all the functions in a separate file, just put them all here. Just do it once and everything here gets exported.
>> Scott Moss: Cool, any other questions?
>> Student: Are there any security implications to be aware of, or is everything pretty handled for you by Next.js as far as security goes?

[00:08:13]
>> Scott Moss: There are some foot guns, you could expose secret values because technically none of your code from this gets bundled in the client. But if you use environment variables in such a way, you could expose them to the client. I forgot the exact scenario in which that can happen, cuz I think they mostly have made it almost impossible for you to do that now since Next.js 15.

[00:08:40]
But there used to be a case where Server Actions could leak sensitive environment variables on a client. But other than that, I haven't come across anything. And I mean, after we do this and you can look at the network tab and you can kind of see what's going on underneath the hood to see what they're sending and what they're not sending.

[00:08:59]
But no, it's essentially just creating routes for you. Password is not valid. Cool, assuming we passed all that, let's create a session and let's tell the client that, hey, you're good to go. So wait, create session for the user.id, and then return { success: true, oops. With message: 'Signed in successfully', cool.

[00:09:34]
So we got that. So good practice to wrap all this stuff in a try catch just in case something happens. You should always do that on anything server side, so I'll just do that. Try,
>> Scott Moss: Catch, there we go. And then, if there was an error, then we'll just do this.

[00:10:03]
No, definitely wanna log that error, so send it to your error service if you have one.
>> Scott Moss: 'Something bad happened', cool. And what's this talking about? Success message, you gotta do a message too. I'll do the same thing, cool. Okay, so that's sign in. Sign up is a little less work, but mostly the same, so let's do that.

[00:10:55]
>> Scott Moss: Cool, pretty much the same thing when it comes to getting the data and validating. So we can just copy that, it's gonna be pretty much the same schema too. I don't think I made a different schema, I did. Why did I make it? Yeah, there's a confirmPassword, that's right, forgot about that.

[00:11:14]
So let's get the confirmPassword: from formData.get('confirmPassword'), cool. So look at that, change this to sign up schema.
>> Scott Moss: If (!validationResult.success) gonna return success: false,.
>> Scott Moss: What did I put down here? Make sure it's exactly the same. 'Validation failed', and then errors would be, yeah, those validation errors there.

[00:12:06]
So call validationResults.error.flatten().filledErrors, yeah, cool. Next thing we wanna do is, okay, cool, if that's all good, we want to attempt to. Well, in this case it's checking to see if the user's in the database. But what you would typically do is, on the database level is just make sure you have a unique constraint on the email.

[00:12:37]
So the database would just store an error so you don't have to check first, that would be pretty redundant, but just being explicit here. So just check to make sure the user is there, and then if it's not, then we wanna kinda do the same thing, so.
>> Scott Moss: ExistingUser = await getUserbyEmail().

[00:13:01]
This will be (data.email). If there is an (existingUser), return success: false, message: 'You already have'. I don't know what you would put here to not spook somebody into giving away that somebody already has an account. You'd be like, 'Nah', [LAUGH] cuz if I put this email already exists, I'm like, I knew they had an account here.

[00:13:35]
So I don't know what you would put there.
>> Scott Moss: 'Stop trying to spoof me', something like that, cool. And then from there, then yeah, you actually would create the user, create the session success: true, all that. So let's try to create the user. If the user doesn't get created for some reason, we'll throw an error.

[00:14:01]
So user = await createUser with the data.email and then a data.password. If (!user) for some reason, you just return something like this.
>> Scott Moss: Cool, and then from there we can await the session with the (user.id) and then return success: true, message: 'Account created'. There we go, and should have did a try catch at the top, but I'm just gonna do it now.

[00:15:01]
>> Scott Moss: Whoa, whoa, whoa.
>> Scott Moss: And there's an error. This is log it and return success: false, message: 'something bad happened'. And there we go, should be good, cool. Any questions on those?
>> Scott Moss: Okay, sign out is pretty simple, you just delete the session. So let's just make that right quick.

[00:16:00]
>> Scott Moss: You just say await deleteSession().
>> Scott Moss: There we go, and then if you couldn't, then yeah, throw the error. Yeah, I guess in this case if you couldn't, you probably just wanna throw the error. So I guess I'm just catching it to log it and report it to sentry or something, I'm so used to using sentry.

[00:16:35]
>> Scott Moss: There we go, and then, whatever, regardless, error or not, this finally what we're gonna do is try to redirect, or I guess in this case. Yeah, I guess if you sign out, no matter what, we always wanna redirect you. So we can use this redirect method from Next.js and we can say redirect them to sign in after they sign out, error or not.

[00:17:01]
>> Student: Quick question, that redirect is essentially running on a function on the server, right?
>> Scott Moss: Mm-hm.
>> Student: So let's say, Next.js does the redirect on the client side? Because the server is just indicating it to the client side that it needs to do a redirect, or how that redirect actually work?

[00:17:22]
>> Scott Moss: Great question. Yeah, question is, how does this redirect happening if it's running on the server? Is Next.js telling a client? Nope, it's so much simpler than that. It's the same way you would do a redirect on any server you've ever done. It's literally redirecting the request with either a status code 303, whatever 300 status code you wanna redirect it to permanent, temporary, whatever.

[00:17:45]
It's literally doing that cuz it is essentially a request. They just do such a good job with the SDK of this that it doesn't feel like you're in a request. But this is a route handler, essentially, it just doesn't feel like it. It's just like it's using a regular function, but you have access to the full request, including redirecting, cookies, headers, everything, yeah.

[00:18:16]
Only if it's a use server. If you don't have that, it's just a regular function and it will throw an error if you try to redirect. You're not even in a request, or you try to use cookies, you don't have access to the request. So this means make these API routes.

[00:18:34]
Whenever you see this, I want you to think make these API routes, that's how I think about it. Obviously, we're not using this right now, and a lot of stuff that we're using hasn't even been implemented, so it's just not gonna work. So what we're gonna have to do next is implement the client side of this.

[00:18:50]
So the forms that actually call this, right? And then we'll have to implement these things like getUserbyEmail and, where's the other one? Is that the only one I had? GetUserbyEmail, this stuff's already done. So yeah, I think it's just getUserbyEmail that we have to make.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Start a 7-Day Free Trial