Transcript
-
Let's go ahead and introduce forms in a very basic state by rigging one up for our Redis FlushDB button. Up till now, that button doesn't really do much.
-
So we can click on it, and it's just not going to do anything. We went to a hash, but that's about it. So we can get rid of that. And let's go ahead and hide our browser back away, and let's dive back into our home page.
-
I'm going to go ahead and close out some of these files that we're not using anymore, and let's scroll down to that button. So currently we have it as a button. It's an href, just going to a hash, as we saw within the browser when we clicked it.
-
In the most basic state for forms, we're going to want to wrap the button in a form, just like so, and we can indent this. We rigged our button component up to where whenever we pass it as an href,
-
it will be applied as an anchor, and any time href is not added, it will be rendered as a button. So what we can do is just get rid of the href, and now our button's a button,
-
and we can specify the type as "submit" so that now whenever we click our button, that will automatically submit our form. If we give this a save, jump back into our browser,
-
we can verify that because the behavior is now going to change. If we give this button a click, our page will do a refresh. As you can see, our image is just re-rendered, and now we're at a question mark page.
-
Let's get rid of that real quick. Okay, cool. Let's hide this back once more. What we need is for a URL for our form to act against
-
to perform some action for the form's posting. So for that, we can define an action. This action is going to need a URL that we have defined within a route.
-
If we dive into our routes, I'm going to hold Command or Ctrl+P to open up our file explorer. We can dive into our routes file, and right down here we have our router.delete,
-
and we particularly want to use our flush method, so that's redis.flush. So let's dive back into our home page,
-
and let's add in an action for route redis.flush, just like so. One last thing that we need to do, though, is specify a method. For HTML forms, this method is going to be "post."
-
By default, HTML forms aren't going to support delete, put, patch, like we have the ability to define within our routes. So we have this route defined as a delete,
-
but we need it to be a post in order for our HTML form to actually be able to use this. So we'll see in the next lesson how we can keep this as delete,
-
but for right now, let's switch this to post so that we can move onward. Let's dive back into our home page, and let's give everything a save. Let's dive back into our browser, let's open up our inspector,
-
and let's dive into the network tab. Let's switch our network tab over to all, and we're also going to want to go into the gear icon and persist our logs. Whenever we click the flush redisdb button,
-
our page will do a refresh and our log will clear out by default, but with that persist logs on, we'll still be able to see and inspect our network requests as we refresh our page.
-
Cool. So now we can go ahead and click our flush redisdb button, and just like so, we see a bunch of requests go out, but if we scroll up to the top, we should see the initial request that went out
-
that caused all of these subsequent requests from our page refreshing. And that's a post request to our flush method. If we dive into that, we can see that we got back a 302 found.
-
Within the response headers, we can scroll down a little bit further, and we can see that it sent back a redirect location to slash, so our home page, essentially redirecting us back to where we were.
-
If we take a look at the request, we didn't send any information. If we take a look at the response, it's our page. Cool. So we can hide that away.
-
Looking at our network request, everything appears to have worked properly, but if we dive into our terminal, we should see an invalid or expired CSRF token warning for this particular request.
-
Furthermore, if we dive back into our text editor, let's scroll up to our controllers, and let's go into our redis controller, particularly within our flush method, as this is the method that we're calling with our form.
-
If we add a console.log flushing redis database here, we'll give that a save. Let's jump back into our browser, and let's click our button once more.
-
We see the exact same behavior, everything refreshed. If we scroll down, we'll see another flush request go out right about here, and if we dive back into our terminal, we're going to see that invalid or expired CSRF token warning again,
-
but we're also not going to see that console.log that we have. So we're never actually reaching our flush route handler right here, and our cache is never actually getting flushed
-
because of that CSRF warning. CSRF stands for cross-site request forgery, and it's a protection system provided by AdonisJS Shield
-
to prevent unauthorized submissions of forms on behalf of authenticated users within our application. Whenever you're working with session-based applications,
-
CSRF protection is a must-have to help protect your users and your application from unauthorized access to that authenticated user session
-
so that the forms cannot be submitted on that user's behalf by something else. And if we dive into our config shield file, we'll see a CSRF section within here,
-
and by default, whenever we create a new web starter kit application, this is going to be enabled, and this will apply for methods that can post information to our application,
-
like post, put, patch, and delete. So everything is actually working exactly as we expect it. There's just one thing that we need to do to actually make this work within our application.
-
So if we dive back into our home page, back into our form, all that we need to do is specify a CSRF token within our form,
-
and AdonisJS will both set and check that token for us to verify that it is an authorized action being performed. We can easily provide in a CSRF token within our form
-
by doing double curly braces and calling the CSRF field function, just like so. This will automatically apply in an input that's hidden within our form
-
with a CSRF token as its value. So we can give this a save. Let's jump back into our browser, and let's inspect our page. So we can dive into our div, we can dive into our fixed element,
-
and dive into our form, and sure enough, there is that hidden input called _CSRF with our CSRF token value within it. Let's dive back into our network tab,
-
and let's clear our current log out so that it's a little bit easier to work with, and let's attempt to flush our Redis DB one more time.
-
Okay, we saw everything go out, and it behaved the exact same as it did before. If we inspect this request and take a look at the request data, we're now going to see a CSRF token being sent up with the form data
-
so that AdonisJS can check it and verify it's valid. And furthermore, if we inspect our terminal, we're going to see that that CSRF token warning is no longer there,
-
and we indeed did flush our Redis database. Cool, so everything's now working exactly as we expected, now that we're providing in that CSRF token with our form submission.
Form Basics and CSRF Protection
We'll cover the basics of working with HTML forms in AdonisJS and how they incorporate Cross-Site Request Forgery (CSRF) protection via AdonisJS Shield.
Join the Discussion 4 comments
-
Are you going to do anything with form validation? I'm trying to write a custom rule for Vine and the docs don't seem to say what the function should return. In Laravel a validation function returned true or false but I cannot figure it out from the example they use in the Vine docs.
1-
Responding to frp
Yeah, validation will come with module 7, which focuses on form flow. The database modules are the last large modules, so things should start flowing quickly thereafter :)
Anything that doesn't report an error is considered valid. So, to walk through the example within Vine's documentation:
import { FieldContext } from '@vinejs/vine/types' /** * Options accepted by the unique rule */ type Options = { table: string column: string } /** * Implementation */ async function unique( value: unknown, options: Options, field: FieldContext ) { /** * 1. If the value isn't a valid string, we'll bail out here * The "string" rule will handle this particular validation * vine.string().use(unique({...})) */ if (typeof value !== 'string') { return } // 2. Otherwise, we'll continue validating uniqueness by checking the db const row = await db .select(options.column) .from(options.table) .where(options.column, value) .first() // 3. If value is NOT unique, we'll report the error if (row) { field.report( 'The {{ field }} field is not unique', 'unique', field ) } // If no error was reported by the end of the method // we'll assume everything was valid } export const uniqueRule = vine.createRule(unique)
Copied!0-
Responding to tomgobich
Ok, so the return earlier is just passing on to the next validator, and unless you have that field.report() method, you are good. Thanks, that makes sense.
1-
Responding to frp
-
-
-