Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Design pattern around routes and enforcing OAuth2 scopes #58

Open
ae6rt opened this issue Jan 21, 2016 · 1 comment
Open

Design pattern around routes and enforcing OAuth2 scopes #58

ae6rt opened this issue Jan 21, 2016 · 1 comment

Comments

@ae6rt
Copy link

ae6rt commented Jan 21, 2016

Hi. I was directed to file an issue here by one of your engineers in lieu of posting to an email list dedicated to this project.

I'm evaluating gocraft/web for use in a reference application my colleagues will use to pattern new microservices after in Go. The purpose of the reference app is to make choices around how to structure the code and model dependencies (database, service discovery, etc) configuration/injection so application developers don't have to individually make all these choices all over again themselves.

One of the specific questions I have around gocraft/web concerns how to idiomatically integrate OAuth2 token validation for a given endpoint. An endpoint is defined as the combination of an HTTP-verb:url-path.

Take this example from your readme:

router.Put("/users/:id", (*YourContext).UsersUpdate)

Imagine that you want to constrain consumption of this resource to users who present an OAuth2 bearer token with a specific scope. Say this scope has to be "admin/update-user". I'm wondering how you recommend enforcing such a constraint in a canonical gocraft/web app. One way is to insert a middleware that extracts the token from the request, places derived token detail information including scope into YourContext, then have the UsersUpdate method either allow or disallow the request based on some allowed, essentially hardcoded scope in the YourContext method itself.

For example, UsersUpdate pseudocode might look like this:

func (c *YourContext) UsersUpdate(rw web.ResponseWriter, req *web.Request) {
    if c.token.scope != "admin/update-user" {
       return unauthorized
    } 
    do work
}

The downside of this approach is that the endpoints are burdened with this sort of security check. A closure around the method might work, which closes over the required scope, but frankly I'm not sure how to articulate that in this situation.

Another way, which I cannot quite yet see my way clear to in idiomatic gocraft/web, is to imbue the middleware code with a table of tuples that look like HTTP-verb:URL-path:required-scope(s) and allow or deny the request in the middleware before the request makes it to UsersUpdate. This suggests some duplication in the articulation of the tuple with information already in the router.PUT() path-with-placeholders. iow, "/users/:id" would appear twice: once in the router.PUT() binding, and once in the middleware constraint rules. Even then, the middleware would have to match the request against the /path/:placeholder template, if such a match is easily performed with some gocraft/web stateless function.

Thank you for reading this far. Does the community have recommendations or strong patterns on how to handle such a requirement in gocraft/web?

Many thanks.
Mark

@kiwih
Copy link

kiwih commented Jun 16, 2016

I use gocraft a lot, but don't typically check the issues page, so I hope this is still relevant.

The way we manage this situation in our organisation is to have multiple routers, each with different security/login middlewares, to minimise code repetition.

Such a setup might look like this:

rootRouter := web.New(Context{})
rootRouter.Middleware((*Context).AssignSessionsMiddleware)
...
rootRouter.Middleware((*Context).LoadUserMiddleware) //responsible for signing in the user if possible
...
//now we have url endpoints that *could* have users signed in but are not compulsory
rootRouter.Get(HomeUrl.String(), (*Context).HomeHandler)
rootRouter.Get(PricingUrl.String(), (*Context).PricingHandler)
rootRouter.Get(HelpUrl.String(), (*Context).HelpHandler)
...

//now, define a router that requires the user to be signed in
loggedInRequiredRouter := rootRouter.Subrouter(LoggedInRequiredContext{}, "/") 
loggedInRequiredRouter.Middleware((*LoggedInRequiredContext).RequireLoggedInMiddleware) //if user is not logged in, redirect them to login page
... //any other middlewares     

//now url endpoints that require the user to be logged in
loggedInRequiredRouter.Get(ViewAccountUrl.String(), (*LoggedInRequiredContext).ViewAccountHandler)

This sort of setup means that I don't ever have to have the

if c.token.scope != "admin/update-user" {
       return unauthorized
}

from your code, as I just keep defining contexts and middlewares as I need them (eg AdminRequiredContext, RequireAdminMiddleware, etc etc)

I hope this helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants