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 scopeFull 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:
Related
- Basic & API-key auth — validating a pre-shared credential.
- Composed auth — mixing schemes and extracting JWT scopes.