...

Text file src/github.com/ory/fosite/docs/how-tos/client_credentials_grant.md

Documentation: github.com/ory/fosite/docs/how-tos

     1# Client Credentials Grant
     2
     3The following example configures a *fosite* *OAuth2 Provider* for issuing *JWT* *access tokens* using  the *Client Credentials Grant*. This grant allows a client to request access tokens using only its client credentials at the *Token Endpoint*(see [rfc6749 Section 4.4](https://tools.ietf.org/html/rfc6749#section-4.4). For this aim, this *how-to* configures:
     4
     5* RSA *JWT Strategy* to sign JWT *access tokens*
     6* *Token Endpoint* http handler
     7* A `fosite.OAuth2Provider` that provides the following services:
     8    * Create and validate [*OAuth2 Access Token Requests*](https://tools.ietf.org/html/rfc6749#section-4.1.3) with *Client Credentials Grant*
     9    * Create an [*Access Token Response*](https://tools.ietf.org/html/rfc6749#section-4.1.4) and
    10    * Sends a [successful](https://tools.ietf.org/html/rfc6749#section-5.1) or [error](https://tools.ietf.org/html/rfc6749#section-5.2) HTTP response to client
    11
    12## Code Example
    13
    14`token_handler.go`
    15```golang
    16package main
    17
    18import (
    19	"net/http"
    20
    21	"github.com/ory/fosite"
    22	"github.com/ory/fosite/handler/oauth2"
    23)
    24
    25type tokenHandler struct {
    26	oauth fosite.OAuth2Provider
    27}
    28
    29func (t *tokenHandler) TokenHandler(w http.ResponseWriter, r *http.Request) {
    30	ctx := r.Context()
    31
    32	// A JWT session allows to configure JWT
    33	// header, body and claims for the *access token*.
    34	// Sessions also keeps data between calls in a flow
    35	// but the client credentials flow only uses the Token Endpoint
    36	session := &oauth2.JWTSession{}
    37
    38	// NewAccessRequest creates an [Access Token Request](https://tools.ietf.org/html/rfc6749#section-4.1.3)
    39	// if the given http request is valid.
    40	ar, err := t.oauth.NewAccessRequest(ctx, r, session)
    41	if err != nil {
    42		t.oauth.WriteAccessError(w, ar, err)
    43		return
    44	}
    45
    46	// NewAccessResponse creates a [Access Token Response](https://tools.ietf.org/html/rfc6749#section-4.1.4)
    47	// from a *Access Token Request*.
    48	// This response has methods and attributes to setup a valid RFC response
    49	// for Token Endpont, for example:
    50	//
    51	// ```
    52	// {
    53	//	"access_token":"2YotnFZFEjr1zCsicMWpAA",
    54	//	"token_type":"example",
    55	//	"expires_in":3600,
    56	//	"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
    57	//	"example_parameter":"example_value"
    58	//  }
    59	//  ```
    60	response, err := t.oauth.NewAccessResponse(ctx, ar)
    61	if err != nil {
    62		t.oauth.WriteAccessError(w, ar, err)
    63		return
    64	}
    65
    66	// WriteAccessResponse writes the Access Token Response
    67	// as a HTTP response
    68	t.oauth.WriteAccessResponse(w, ar, response)
    69}
    70
    71```
    72
    73`main.go`
    74```go
    75package main
    76
    77import (
    78	"crypto/rand"
    79	"crypto/rsa"
    80	"log"
    81	"net/http"
    82	"time"
    83
    84	"github.com/ory/fosite"
    85	"github.com/ory/fosite/compose"
    86	"github.com/ory/fosite/storage"
    87)
    88
    89func main() {
    90	// Generates a RSA key to sign JWT tokens
    91	key, err := rsa.GenerateKey(rand.Reader, 2048)
    92	if err != nil {
    93		log.Fatalf("Cannot generate RSA key: %v", err)
    94	}
    95
    96	var storage = storage.NewMemoryStore()
    97
    98	// Register a test client in the memory store
    99	storage.Clients["test-client"] = &fosite.DefaultClient{
   100		ID:         "test-client",
   101		Secret:     []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar"
   102		GrantTypes: []string{"client_credentials"},
   103	}
   104
   105	// check the api docs of compose.Config for further configuration options
   106	var config = &compose.Config{
   107		AccessTokenLifespan: time.Minute * 30,
   108	}
   109
   110	var oauth2Provider = compose.Compose(
   111		config,
   112		storage,
   113		compose.NewOAuth2JWTStrategy(
   114			key,
   115			// HMACStrategy is used to sign refresh token
   116			// therefore not required for our example
   117			nil,
   118		),
   119		// BCrypt hasher is automatically created when omitted.
   120		// Hasher is used to store hashed client authentication passwords.
   121		nil,
   122		compose.OAuth2ClientCredentialsGrantFactory,
   123	)
   124
   125	accessTokenHandler := tokenHandler{oauth: oauth2Provider}
   126	http.HandleFunc("/token", accessTokenHandler.TokenHandler)
   127	log.Println("serving on 0.0.0.0:8080")
   128	if err := http.ListenAndServe("0.0.0.0:8080", nil); err != nil {
   129		log.Fatal(err)
   130	}
   131}
   132
   133```
   134
   135## To run
   136
   137In one terminal run the http server as follows:
   138
   139```bash
   140$go run .
   1412021/04/26 12:57:24 serving on 0.0.0.0:8080
   142```
   143
   144In a different terminal issue a token as follows:
   145
   146```bash
   147$curl http://localhost:8080/token -d grant_type=client_credentials -d client_id=test-client -d client_secret=foobar
   148{
   149  "access_token": "<redacted>",
   150  "expires_in": 1799,
   151  "scope": "",
   152  "token_type": "bearer"
   153}
   154```

View as plain text