...
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