In a previous post, I described how I added Swagger support to my sample Go fullstack application. Here, I describe how that solution was enhanced to provide API security.

Adding API security involved a complete frontend rewrite and some reasonably well documented changes to the AWS API Gateway based backend: AWS Cognito was chosen as the basis of the security solution as I was already working within the AWS ecosystem.

Limitations of vecty when supporting OAuth Flows

The previous frontend was written using the [vecty](https://github.com/gopherjs/vecty) Go frontend framework; this is certainly a nice framework which proved (for me) a good entry point to Go frontend development. vecty has many positive aspects — the management of DOM elements is efficient, the component model is well thought out and the end result is certainly a reactive frontend experience; however as it is still considered experimental by its authors, limitations are bound to appear at some point — I encountered a significant limitation when trying to add security.

The security model which I wanted to use is a standard OAuth authorization grant model leveraging Proof Key for Code Exchange (PKCE). PKCE is designed for cases in which there is no application backend service to form part of the OAuth flow; mobile and web applications are the primary use cases. An essential characteristic of the PKCE flow is that there is no client secret as the client is an untrusted end device. PKCE does not fundamentally differ from the well-known OAuth grant flow; rather it is an adaptation — it still requires login to an authentication provider, redirecting to a known callback URI with a code which can then be used to obtain an access token which can be used to access secured resources.

As noted, PKCE requires the use of a callback; in the PKCE case, it is assumed that this callback is handled at the client side — for mobile applications, for example, it’s possible that a custom URI scheme (e.g. myapp://) could be used which is handled by the device operating system. For web applications without backend support, this must be handled by a frontend router.

At present, vecty does not have built in support for routes on the frontend; the [vecty-router](https://github.com/marwan-at-work/vecty-router) project partially addresses this but I encountered issues regarding how it could be used to navigate to an alternate URI when the callback was triggered — components which had not been rendered before would not render properly. For this reason, I started to look at alternative solutions.

Enter vugu…

[vugu](https://www.vugu.org/)is another frontend Go framework which was strongly inspired by Vue.js but has evolved somewhat differently primarily due to the design of Go. Unlike vectyvugu files contain a mix of HTML, CSS and Go — this can make your editor a little more confused but does result in more compact representation of the application logic. vugu takes this description of the application and parses it into a set of native Go files which can then be compiled down to WebAssembly and run in the browser.

vugu does have some support for routing although it is still quite new: it comprises a hierarchical model in which nested components can be updated based on a change to the URI in the browser: the router also enables the browser URI to be updated by the application and this can result in a rerendering of page content.

As vugu has a strong frontend bias, it comes with a development server which does not perform any explicit route handling and simply serves the same content irrespective of which endpoint is called: hence all route handling with the development server is performed in the frontend.

Implementation of the Todo Application in vugu

The first step was to reimplement the [vecty](https://github.com/seanrmurphy/go-vecty-swagger) based To Do List application using vugu. In general, this was very straightforward; the vugu model in which actions on page elements can be directly linked to Go functions made the implementation simpler than the approach used in vecty which used actions, listeners and dispatchers.

vugu has some nice primitives which make it easy to display some HTML content conditionally; there are also nice looping constructs which makes it easy to display lists of items — this was useful for displaying the todos in this case. A nice design feature of vugu is the support for calling a specific function prior to rendering (with BeforeBuild()), such that data can be processed or perhaps retrieved from a server before entering the time-critical rendering loop.

Working with the vugu devserver was also a nice experience which increased the cadence of the workflow — as with other frontend frameworks, application changes are automatically picked up by the devserver and reloaded within the browser for testing and validation.

Adding Security — integrating an OAuth flow

To use the OAuth PKCE flow, logic had to be added to the frontend to navigate to the authentication service with the appropriate query parameters and handle the callback URI.

PKCE works by creating a so-called Code Verifier and associated challenge on the frontend and passing the latter to the authentication server. After authentication, the authentication server redirects to the callback with a code. For my initial attempt, I tried to use http://localhost:8844/callbackas the callback URI and have this redirect back to the main landing page — however the vugurouter is still evolving and I had issues changing to a new URI when rendering content given a different URI. For this reason, I just used the main landing page as the callback URI and added logic to parse the code that is returned. The frontend then obtains an access token from the authentication service using the code and the code verifier; this access token can then be used when calling the API.

func (c *Root) createCognitoURI(clientName string, p CognitoParameters) (u url.URL) {
  u = url.URL{
    Scheme: "https",
    Host:   clientName + ".auth.<AWS-REGION>.amazoncognito.com",
    Path:   "oauth2/authorize",
    Opaque: "//" + clientName + ".auth.<AWS-REGION>.amazoncognito.com/oauth2/authorize",
  }
  v, _ := query.Values(p)
  u.RawQuery = v.Encode()
  return
}
func (c *Root) Login() {
  challenge := LoginData.CodeVerifier.CodeChallengeS256()
  challengeMethod := "S256"
  clientName := "<CLIENT-NAME>"
  clientID := "<CLIENT-ID>"
  p := CognitoParameters{
    ResponseType:        "code",
    ClientID:            clientID,
    RedirectURI:         "http://localhost:8844",
    State:               "initial-state",
    IdentityProvider:    "COGNITO",
    IDPProvider:         "",
    Scope:               "profile",
    CodeChallengeMethod: challengeMethod,
    CodeChallenge:       challenge,
  }
  q := c.createCognitoURI(clientName, p)
  window := js.Global().Get("window")
  window.Call("open", q.String(), "_self")
}

The current solution is one in which a Log In button is rendered on the landing page — clicking on this causes an appropriate URI to be created for the authentication server which includes the query parameters including the challenge mentioned above, the client ID, the callback URI and a state parameter (see code snippet). After the login is performed, the browser is redirected back to the landing page with a codeas a query parameter; an access token is then obtained using the code which is stored within the frontend application and is used to obtain the todo list from the secure REST API.

#go #swagger #webassembly #fullstack-development #aws

Fullstack Go: Adding API Security
1.30 GEEK