📖 3 min read (~ 600 words).

OAuth2 access-code

The other auth examples validate a token the client already has. This one runs the full OAuth2 access-code handshake — redirecting the user to an identity provider (Google), receiving a callback, exchanging the code for a token, then using that token to authenticate API calls.

Tip

Source: oauth2/. Generated with swagger generate server --name oauthSample --spec ./swagger.yml --principal models.Principal. The handshake lives in the hand-written restapi/implementation.go.

The scheme

The spec declares an oauth2 scheme with the accessCode flow and its authorization/token URLs. Unlike composed-auth — which only borrows the oauth2 type to carry scopes — here the URLs are real and drive an actual handshake:

securityDefinitions:
  OauthSecurity:
    type: oauth2
    flow: accessCode
    authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth'
    tokenUrl: 'https://www.googleapis.com/oauth2/v4/token'
    scopes:
      admin: Admin scope
      user: User scope

Full source: oauth2/swagger.yml

Note

go-swagger does not implement the OAuth2 workflow for you: the generator produces the authenticator hook and the routing, but the redirect/callback/exchange dance is application code. That’s exactly what this example provides.

Step 1 — redirect to the provider

The public /login endpoint sends the user to Google’s consent screen, using the golang.org/x/oauth2 config built in implementation.go:

func login(r *http.Request) middleware.Responder {
	// implements the login with a redirection
	return middleware.ResponderFunc(
		func(w http.ResponseWriter, _ runtime.Producer) {
			http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
		})
}

Full source: oauth2/restapi/implementation.go

Step 2 — handle the callback and exchange the code

Google redirects back to /auth/callback with a state and a code. The handler verifies state, then exchanges the code for an access token via the oauth2 client:

func callback(r *http.Request) (string, error) {
	// we expect the redirected client to call us back
	// with 2 query params: state and code.
	// We use directly the Request params here, since we did not
	// bother to document these parameters in the spec.

	if r.URL.Query().Get("state") != state {
		log.Println("state did not match")
		return "", errors.New("state did not match")
	}

	myClient := &http.Client{}

	parentContext := context.Background()
	ctx := oidc.ClientContext(parentContext, myClient)

	authCode := r.URL.Query().Get("code")
	log.Printf("Authorization code: %v\n", authCode)

	// Exchange converts an authorization code into a token.
	// Under the hood, the oauth2 client POST a request to do so
	// at tokenURL, then redirects...
	oauth2Token, err := config.Exchange(ctx, authCode)
	if err != nil {
		log.Println("failed to exchange token", err.Error())
		return "", errors.New("failed to exchange token")
	}

	// the authorization server's returned token
	log.Println("Raw token data:", oauth2Token)
	return oauth2Token.AccessToken, nil
}

Full source: oauth2/restapi/implementation.go

Step 3 — authenticate API calls with the token

Every protected endpoint runs through the generated OauthSecurityAuth hook. It validates the bearer token (here by calling Google’s userinfo endpoint) and returns the principal — the token string itself in this minimal example:

api.OauthSecurityAuth = func(token string, scopes []string) (*models.Principal, error) {
	_ = scopes

	ok, err := authenticated(token)
	if err != nil {
		return nil, errors.New(401, "error authenticate")
	}
	if !ok {
		return nil, errors.New(401, "invalid token")
	}
	prin := models.Principal(token)

	return &prin, nil
}

Full source: oauth2/restapi/configure_oauth_sample.go

Setup

Register an OAuth client at the Google credentials console, set the callback URL to http://127.0.0.1:12345/api/auth/callback, and put the resulting client ID/secret into the var block of implementation.go. Then:

$ go run ./oauth2/cmd/oauth-sample-server/main.go --port 12345
# open http://127.0.0.1:12345/api/login in a browser, log in, copy the token
$ curl -i -H 'Authorization: Bearer <TOKEN>' http://127.0.0.1:12345/api/customers