github.com/kaisawind/go-swagger@v0.19.0/docs/tutorial/oauth2/README.md (about)

     1  # Oauth2 Authentication sample: AccessCode workflow
     2  
     3  The full code of this example is [here][example_code].
     4  
     5  This example illustrates a complete OAuth2 handshake.
     6  
     7  We want to implement a simple access control based on a user's Google account (i.e. OpenID).
     8  
     9  Personas:
    10  
    11  - the user logs in on its Google account, which returns an access token that we will use
    12    with our API. This mechanism follows the 'accessCode' OAuth2 workflow.
    13  
    14  
    15  ### Swagger specification
    16  
    17  Given the following security definitions (in `swagger.yml` specification document):
    18  
    19  ```yaml
    20  securityDefinitions:
    21    OauthSecurity:
    22      type: oauth2
    23      flow: accessCode
    24      authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth'
    25      tokenUrl: 'https://www.googleapis.com/oauth2/v4/token'
    26      scopes:
    27        admin: Admin scope
    28        user: User scope
    29  ```
    30  
    31  We specify the following security requirements:
    32  
    33  - A default requirements for all endpoints: users need to be authenticated within the "user" scope by providing 
    34  a OAuth token (e.g. Authentication: Bearer header or `accss_token` query parameter).
    35  
    36  ```yaml
    37  security:
    38    - OauthSecurity:
    39      - user
    40  ```
    41  
    42  - Login and callback endpoints are not restricted: this is made explicit by overriding the default security requirement with an empty array.
    43  
    44  ```yaml
    45  paths:
    46    /login:
    47      get:
    48        summary: login through oauth2 server
    49        security: []
    50  
    51  ...
    52  
    53    /auth/callback:
    54      get:
    55        summary: return access_token
    56        security: []
    57  ```
    58  
    59  We need to specify a security principal in the model, to generate the server. Operations will be passed this principal as 
    60  parameter upon successful authentication:
    61  
    62  ```yaml
    63  definitions:
    64    ...
    65    principal:
    66      type: string
    67  ```
    68  
    69  In this example, the principal (descriptor of an identity for our API) 
    70  is just a string (i.e. the token itself).
    71  
    72  ### Generate the server 
    73  
    74  ```shell
    75  swagger generate server -A oauthSample -P models.Principal -f ./swagger.yml
    76  ```
    77  
    78  ### Prepare the configuration
    79  
    80  In `restapi/implementation.go` (this is not a generated file), we defined an
    81  implementation for our workflow.
    82  
    83  First, we need some extra packages to work with OAuth2, OpenID and HTTP redirections:
    84  
    85  ```go
    86  import (
    87  	oidc "github.com/coreos/go-oidc"            // Google OpenID client
    88  	"golang.org/x/net/context"
    89  	"golang.org/x/oauth2"                       // OAuth2 client
    90  )
    91  ```
    92  
    93  ```go
    94  var (
    95      // state carries an internal token during the oauth2 workflow
    96      // we just need a non empty initial value
    97  	state = "foobar" // Don't make this a global in production.
    98  
    99      // the credentials for this API (adapt values when registering API)
   100  	clientID     = "" // <= enter registered API client ID here
   101  	clientSecret = "" // <= enter registered API client secret here
   102  
   103      //  unused in this example: the signer of the delivered token
   104  	issuer       = "https://accounts.google.com"
   105  
   106      // the Google login URL
   107  	authURL      = "https://accounts.google.com/o/oauth2/v2/auth"
   108  
   109      // the Google OAuth2 resource provider which delivers access tokens
   110  	tokenURL     = "https://www.googleapis.com/oauth2/v4/token"
   111  	userInfoURL  = "https://www.googleapis.com/oauth2/v3/userinfo"
   112  
   113      // our endpoint to be called back by the redirected client
   114  	callbackURL  = "http://127.0.0.1:12345/api/auth/callback"
   115  
   116      // the description of the OAuth2 flow
   117  	endpoint = oauth2.Endpoint{
   118  		AuthURL:  authURL,
   119  		TokenURL: tokenURL,
   120  	}
   121  
   122  	config = oauth2.Config{
   123  		ClientID:     clientID,
   124  		ClientSecret: clientSecret,
   125  		Endpoint:     endpoint,
   126  		RedirectURL:  callbackURL,
   127  		Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
   128  	}
   129  )
   130  ```
   131  
   132  ### Configure the API in `restapi/configure_auth_sample.go`
   133  
   134  ```go
   135  func configureAPI(api *operations.OauthSampleAPI) http.Handler {
   136  	// configure the api here
   137  	api.ServeError = errors.ServeError
   138  
   139  	// Set your custom logger if needed. Default one is log.Printf
   140  	// Expected interface func(string, ...interface{})
   141  	//
   142  	// Example:
   143  	api.Logger = log.Printf
   144  
   145  	api.JSONConsumer = runtime.JSONConsumer()
   146  
   147  	api.JSONProducer = runtime.JSONProducer()
   148  
   149  	api.OauthSecurityAuth = func(token string, scopes []string) (*models.Principal, error) {
   150          // This handler is called by the runtime whenever a route needs authentication 
   151          // against the 'OAuthSecurity' scheme.
   152          // It is passed a token extracted from the Authentication Bearer header, and 
   153          // the list of scopes mentioned by the spec for this route.
   154  
   155          // NOTE: in this simple implementation, we do not check scopes against  
   156          // the signed claims in the JWT token.
   157          // So whatever the required scope (passed a parameter by the runtime), 
   158          // this will succeed provided we get a valid token.
   159  
   160          // authenticated validates a JWT token at userInfoURL
   161  		ok, err := authenticated(token)
   162  		if err != nil {
   163  			return nil, errors.New(401, "error authenticate")
   164  		}
   165  		if !ok {
   166  			return nil, errors.New(401, "invalid token")
   167  		}
   168  
   169          // returns the authenticated principal (here just filled in with its token)
   170  		prin := models.Principal(token)
   171  		return &prin, nil
   172  	}
   173  
   174  	api.GetAuthCallbackHandler = operations.GetAuthCallbackHandlerFunc(func(params operations.GetAuthCallbackParams) middleware.Responder {
   175          // implements the callback operation
   176  		token, err := callback(params.HTTPRequest)
   177  		if err != nil {
   178  			return middleware.NotImplemented("operation .GetAuthCallback error")
   179  		}
   180  		log.Println("Token", token)
   181  		return operations.NewGetAuthCallbackDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(token)})
   182  	})
   183  
   184  	api.GetLoginHandler = operations.GetLoginHandlerFunc(func(params operations.GetLoginParams) middleware.Responder {
   185          // implements the login operation
   186  		login(params.HTTPRequest)
   187  		return middleware.NotImplemented("operation .GetLogin has not yet been implemented")
   188  	})
   189  
   190  	api.CustomersCreateHandler = customers.CreateHandlerFunc(func(params customers.CreateParams, principal *models.Principal) middleware.Responder {
   191          // other API endpoint ...
   192  		log.Println("hit customer API")
   193  		return middleware.NotImplemented("operation customers.Create has not yet been implemented")
   194  	})
   195  
   196  	api.CustomersGetIDHandler = customers.GetIDHandlerFunc(func(params customers.GetIDParams, principal *models.Principal) middleware.Responder {
   197          // other API endpoint ...
   198  		log.Println("hit customer API")
   199  		return middleware.NotImplemented("operation customers.GetID has not yet been implemented")
   200  	})
   201  
   202  	api.ServerShutdown = func() {}
   203  
   204  	return setupGlobalMiddleware(api.Serve(setupMiddlewares))
   205  }
   206  ```
   207  
   208  We set the following implementation for authentication in `restapi/implementation.go` (**this is not generated code** and may be fully customized):
   209  
   210  - Redirecting to the login page
   211  
   212  ```go
   213  func login(r *http.Request) string {
   214  	// implements the login with a redirection and an access token
   215  	var accessToken string
   216  	wG := r.Context().Value(ctxResponseWriter).(http.ResponseWriter)
   217  	http.Redirect(wG, r, config.AuthCodeURL(state), http.StatusFound)
   218  	return accessToken
   219  }
   220  ```
   221  - Retrieving the access token
   222  
   223  ```go
   224  func callback(r *http.Request) (string, error) {
   225      // we expect the redirected client to call us back 
   226      // with 2 query params: state and code.
   227      // We use directly the Request params here, since we did not 
   228      // bother to document these parameters in the spec.
   229  
   230  	if r.URL.Query().Get("state") != state {
   231  		log.Println("state did not match")
   232  		return "", fmt.Errorf("state did not match")
   233  	}
   234  
   235  	myClient := &http.Client{}
   236  
   237  	parentContext := context.Background()
   238  	ctx := oidc.ClientContext(parentContext, myClient)
   239  
   240  	authCode := r.URL.Query().Get("code")
   241  	log.Printf("Authorization code: %v\n", authCode)
   242  
   243      // Exchange converts an authorization code into a token.
   244      // Under the hood, the oauth2 client POST a request to do so
   245      // at tokenURL, then redirects...
   246  	oauth2Token, err := config.Exchange(ctx, authCode)
   247  	if err != nil {
   248  		log.Println("failed to exchange token", err.Error())
   249  		return "", fmt.Errorf("failed to exchange token")
   250  	}
   251  
   252      // the authorization server's returned token
   253  	log.Println("Raw token data:", oauth2Token)
   254  	return oauth2Token.AccessToken, nil
   255  }
   256  ```
   257  
   258  
   259  - Validating the token
   260  
   261  ```go
   262  func authenticated(token string) (bool, error) {
   263  	// validates the token by sending a request at userInfoURL
   264  	bearToken := "Bearer " + token
   265  	req, err := http.NewRequest("GET", userInfoURL, nil)
   266  	if err != nil {
   267  		return false, fmt.Errorf("http request: %v", err)
   268  	}
   269  
   270  	req.Header.Add("Authorization", bearToken)
   271      
   272  	cli := &http.Client{}
   273  	resp, err := cli.Do(req)
   274  	if err != nil {
   275  		return false, fmt.Errorf("http request: %v", err)
   276  	}
   277  	defer resp.Body.Close()
   278  
   279  	_, err = ioutil.ReadAll(resp.Body)
   280  	if err != nil {
   281  		return false, fmt.Errorf("fail to get response: %v", err)
   282  	}
   283  	if resp.StatusCode != 200 {
   284  		return false, nil
   285  	}
   286  	return true, nil
   287  }
   288  ```
   289  
   290  ### Register the callback URL 
   291  Register your API at [google oauth2 server][google_credential], with 
   292  an OAuth ID.  
   293  Make sure that the callback URL is the same as set in the above code (``./restapi/configure_auth_sample.go``), e.g.:
   294  
   295  ```
   296  http://127.0.0.1:12345/api/auth/callback
   297  ```
   298  
   299  ![Google api screenshot](https://github.com/go-swagger/go-swagger/blob/master/examples/oauth2/img/google-api.png)
   300  
   301  >**NOTE:** you may specify a client ID for your API during the registration process.
   302  >A password (the API client's secret) is then delivered.
   303  >Those are the credentials of the API itself, not the end user.
   304  >Put these values (client ID and client's secret) in the initial 
   305  >var declarations in `implementation.go`.
   306  
   307  ### Run the server
   308  
   309  ```shell
   310  go run ./cmd/oauth-sample-server/main.go --port 12345
   311  ```
   312  
   313  ### Login to get the access token
   314  
   315  Get the access token through Google's oauth2 server. 
   316  
   317  Open the browser and access the API login url on: 
   318  http://127.0.0.1:12345/api/login,  which will direct you to the Google 
   319  login page. 
   320  
   321  Once you login with your google ID (e.g., your gmail account), the oauth2  
   322  ``access_token`` is returned and displayed on the browser.
   323  
   324  ### Exercise your authorizer
   325  
   326  ``TOKEN`` is obtained from the previous step.
   327  
   328  Now we may use this token to access the other endpoints published by our API.
   329  
   330  Let's try this with curl. Copy the received token and reuse it as shown below:
   331  ```shellsession
   332  ± ivan@avalon:~  
   333   »  curl -i  -H 'Authorization: Bearer TOKEN' http://127.0.0.1:12345/api/customers
   334  ```
   335  ```http
   336  HTTP/1.1 501 Not Implemented
   337  Content-Type: application/keyauth.api.v1+json
   338  Date: Fri, 25 Nov 2016 19:14:14 GMT
   339  Content-Length: 57
   340  
   341  "operation customers.GetID has not yet been implemented"
   342  ```
   343  
   344  Use an random string as the token:
   345  
   346  ```shellsession
   347  ± ivan@avalon:~  
   348   » curl -i  -H 'Authorization: Bearer RAMDOM_TOKEN' http://127.0.0.1:12345/api/customers
   349  ```
   350  ```http
   351  HTTP/1.1 401 Unauthorized
   352  Content-Type: application/keyauth.api.v1+json
   353  Date: Fri, 25 Nov 2016 19:16:49 GMT
   354  Content-Length: 47
   355  
   356  {"code":401,"message":"unauthenticated for invalid credentials"}       
   357  ```
   358  
   359  [google_credential]: https://console.cloud.google.com/apis/credentials/
   360  [example_code]: https://github.com/go-swagger/go-swagger/blob/master/examples/oauth2/