App Cookie Structure
An application-wide cookie structure is created to store authentication state and user email. Constants define session keys, and helper functions retrieve and map session values into a typed app struct.
Context Integration
The app data is injected into the request context so it can be accessed throughout the application. This mirrors the flash message setup and ensures authentication data is consistently available during request handling.
Authentication Middleware
An auth only middleware function is implemented to restrict access to protected routes. It checks the authentication flag from the app cookie and redirects unauthenticated users to the login page.
Middleware Registration Order
Middleware is registered sequentially, with cookie store and app context setup occurring before authentication checks. Proper ordering ensures session data is available when evaluated.
Controller Rendering Update
The render function is extended to propagate the app context through the rendering pipeline. This allows UI components to access authentication state.
Route Protection Strategies
Two approaches are explored: applying middleware individually to routes or grouping routes under a common prefix. While grouping simplifies middleware application, it introduces routing complexities that require additional handling.
Testing and Debugging
The middleware is tested by attempting to access admin routes. Redirect behavior confirms correct authentication enforcement, and routing adjustments are made to resolve prefix-related issues.
Next Steps
The next phase involves implementing the login page, controller logic, and adding CSRF protection to improve security before deployment.
Let's tackle the authentication only middleware. And we need to begin in router, cookies. And in here, I'm gonna create a file called, let's just call it off, package cookies. And we need to repeat much of what we did in the flash messages episode. So we're gonna say var, apki, apki equals to app. Let's call it app cookie.
And we also need this type app key string. Then we need some fields because we're going to set some fields and we need to pull these fields out. And what we get back from this session is interfaces. So we are going to be creating some constant values that specify the name of this field so we can easily pull them out. Right. We need is our
authenticated. The value is authenticated or the value of the field name is going to be is authenticated. We're going to put a user email and it's going to be called user email. Give that a save. Then we create an app struct here and echo context. No, like this.
and then we specify the user email field and there is our then authenticated. Why can't I spell authenticated? It's authenticated. Right, and we're just gonna be good technically call this off, but I like the app naming because we can use it for
other elements than just off. So this is kind of like an app wide cookie that we can use for different kinds of settings. Then we need to create the to get function. So we have in flash, we had the get flashes and get flashes context. We need the same here. So get, get app, text in the ego context.
which turns us an abstract. Where we again say sesh ever session. Let me just grab the import from flash messages. Actually we can take all of this basically and then we're gonna modify it. So where do we have it here? Close that one down.
Add an import. And we of course can have an error. So we need to say app or error as the return values. And app as an empty struct. We don't need flash messages. So we need to say app equals app. And then we pass the ego context.
We need to specify the context here. Then we want to say if blank okay, and then we say sesh and value. Why won't it let me say value and say okay. Do something. We have session and it's valid.
use, I don't know why I'm not getting any, why am I not getting any autocomplete? Let me try and restart here. No. Okay, we're just gonna type it out then. So incest.values, we can pass this out, then de-cated, constant recreated, and then turn that into a boolean. And if it's okay, we're gonna say app is
How then GK that equals to true. Let me just remove. Right. Then we need to repeat this for the user email. So this. And then user email. And we know this is a string. And if that is okay, we say app.user.
email equals the email that we pulled out. And then we simply return app. OK, do we have any errors? We do not have any errors. Great. Then we also need the getAppContext. Do we need to copy? No, let's just say getAppCTX, except a CTX of type context.
context, and here we just return the app. Let's give it a save. And we want to say app CTX OK, CTX value, and then we pass the app key. And then we transform this interface into an app. If it's not OK, we simply return an empty app struct.
And if it's okay, then we return app context. Pretty straightforward. Nothing really new is happening in this setup. It's very similar to what we did in the Flash Messages episode. We're just pulling out values now based on these keys that we defined as constants. Right. So our off app cookie here is ready to
to go, so we can actually go in and create our middleware. And I copy pasted a little bit too much. The value here should of course be app key, and we have app key here. Now we are ready to use the app struct from the other app cookie, let's call it. That we need to
register right. So we are going to create it in here and we're going to say func off only. Again, it's going to be a function that returns an echo or accepts an echo handler func and returns an echo handler func. And in here we don't need to do any setup so we can just return
And again, we need to use our getApp method or function we just created. So we say cookies, getApp, pass there. This is, I need another layer. There's a lot of functions here. There we go. Name this, you see, and then we say if cookies, getApp, pass the echo context, say is,
is authenticated. Again, we can have an error here that is true. So I can't do it in one statement. That's okay. Let's just say app or error and then
If we have an error, we do a lock to ourself saying, let's say, slug.error context, pass the error context, request context, could not get app cookie. Error is error.
Give that a save and then we just return here. What is wrong, we take the, like this, yes. Then we say if app.is authenticated, pass on to the next request in the cycle. And if not, then we say return.
Easy redirect to HTTP status. See other. And what we wanna pass, we wanna redirect them to routes. No, we wanna redirect them to login page. And it's not HTTP, it's the HTTP. So what happens if we can't pull this one out? We don't wanna just let them continue. We're gonna redirect them to the login page.
Yeah, because we could technically just return the abstract, but we will have the same setup that if we, if we couldn't pull out the values and a user was authenticated, then we will still evaluate to false and we will still redirect and to see others. So the same thing is happening here. We just have the proper error check in place with a little error to ourselves that, hey, we could, we could not get the cookie for the app. And this is the error. Right.
Then we can go into our router.gov file, grab all of this, and we're gonna say register app context. We again wanna ignore this if this is on the asset routes, we don't need to authenticate that. Here we will simply say string cookies and app P, and then we're gonna pass the app
ctx and we again need to pull it out and say app ctx or error cookies get app pass the echo context if there is an error let's just do the exact same thing slug error context easy request context could not get app cook key
with the error of error. And do we want to return an error here? We want to, yeah, let's continue on in the request cycle. And then we pass the, we set the string and we do the app context, right. Because again, if you can't get it, we just continue and it will still evaluate to the same,
false error that we have in the middle where I write. So we need to now register. And the order that you register something in actually matters. Because this is all run sequentially, right? So first we run the cookie store, then we run the app context, and then we run the register flash message context. So it's important to note that you need to register them in the right order.
Great. The final thing before we can start using this middleware is we need to extend our render function in the controller. That we have also done before. So we can take all of this and we say appctx with appctx and we pass the app key and we then say app context.
And then instead of passing the echo request context, we pass the app context here. And now we pass the width with app context. So then here we have the parent context of the app context. And then down here, the app context becomes the parent of the flash context. And then we can build this up. And then finally, we just render with the app
We'll server with the flash context and everything is set up and we can start using this authentication cookie app cookie to modify the UIS we need. Great. So I am in the router.go file again here and now we can start to apply this middleware. And if you check the function here, we can see that they accept this
slice this spread out slice with the three dots here. This just means we can add as many echo handler, echo middleware funk as you want. So this is our middleware function. So we will say routes and off only. Again, you're going to pass this to all of the admin pages.
like that. And now you need to be authenticated to access these pages. But we could also, there is something we can use like a group here. So we could actually group all of these with a prefix, which would require us to go in here and then we would remove the prefix from all of these. And then we can group everything at once. So we could do something like remove
all of these and the admin page will just become an empty string and then in here we could group this with the routes admin prefix and add the routes dot off only because all the routes need to have this middleware and now all of these are prefixed with the admin prefix we just need to say
admin group like this. And then instead of calling the echo instance, we call the admin group here. And now all of these route are grouped under the admin prefix, but all of them out also have the off only middleware on them. So let's try and run the development server and see if we can access the admin now.
All right, I'm in our landing page here and let's go in and try and click the admin to see what happens. We get a message not found because we get redirected to the slash locking page. So this is exactly what we want. We haven't created this page yet. That's for the next episode, but our middleware is working as expected. If I go in here to router, go down to the routes, the group here and just do this, save it one more time.
Go back and run it. We go back. Now we can click here and we can access the admin again. We had an issue because yes, I am making a mistake in the routes. We do need the admin prefix. Let's just double check. Am I holding it wrong? I think I am. Let's see now.
We cannot go to this one. Okay, so not as smooth as hoped for. Let's just see, like this. Just run. Now we get this, do we get this? We do not. We should get that. That is really, really strange. Add new article, also not. Okay.
So what am I doing wrong here? We have to admin prefix. That is what we want. And then in here we have the group, right? So everything has a prefix. And this one goes to that page. That should technically work. Why are we not seeing
Let's see that. So if I make this into an E again, do we actually see it? Okay, we can go to it now. I can go to it now, right? So why this is correct slash new.
admin slash new, yes. We do, yes. The router admin group. Let's find one more time now. No.
because this one should go to admin, right. So this is the error that we are, of course, using both of these two. In the UI, we just go directly to slash articles new, and this should live on with the admin prefix. So with the current setup,
We are actually correct in doing it like this. So we can get around this. We would need to do some more setup here. But I think it's just easier that we simply say we either do it like this
and then we would strip the admin prefix here, so say something like strings, replace. So if we do what? We're gonna replace the routes admin prefix. What is the string replace? Old new string, old new. We move it first instance like this.
and the routes yes there we go now if I go to admin it's not found again because I yeah I need to have this to be a empty string now we get it and now we get it so yeah this is
one of the traders we have to make. Do we want to make it easy to reference route and then do this to group it? I would probably just say, let's just add the middleware to all of them instead. But it's there and you can definitely create some logic around your routes to deal with this so that you can have both a group and a route, but you need to handle them a little bit differently. Let's just say routes.
new article page and it goes to the controller and we specify the routes of only and then remove this, remove all of this with E back again and then we add it back. Yeah, so successfully, unsuccessfully show you how you can use screw. But
That is okay. Let's go in here. So I prefer to have this route be referenceable throughout the controllers, throughout the views because it makes our lives easier and it also takes away a lot of errors that we can make. And then we, with the current set of have to make a little trade off that we have to actually specify the middleware on all the routes, but the grouping functionality is there and you can technically create like a
a type of the route and then have it have a path so that the path to get registered and then a URL that gets shown in the UI in the view layer. So we are ready now. Let's just do one more check here. We get redirected to login. So let's in the next episode set up the login page.
the controller and then we also need to deal with some more security around what is known as cross-site request forgery. We will deal with it next episode and then we are ready to almost go live with our block in the current form and we can also manage it without having everyone else being able to access the admin.