Skip to content

Commit

Permalink
docs: add client credentials grant how-to (#589)
Browse files Browse the repository at this point in the history
See #566
  • Loading branch information
narg95 authored Apr 26, 2021
1 parent 6ad9264 commit 893aae4
Showing 1 changed file with 154 additions and 0 deletions.
154 changes: 154 additions & 0 deletions docs/how-tos/client_credentials_grant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Client Credentials Grant

The 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:

* RSA *JWT Strategy* to sign JWT *access tokens*
* *Token Endpoint* http handler
* A `fosite.OAuth2Provider` that provides the following services:
* Create and validate [*OAuth2 Access Token Requests*](https://tools.ietf.org/html/rfc6749#section-4.1.3) with *Client Credentials Grant*
* Create an [*Access Token Response*](https://tools.ietf.org/html/rfc6749#section-4.1.4) and
* 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

## Code Example

`token_handler.go`
```golang
package main

import (
"net/http"

"github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2"
)

type tokenHandler struct {
oauth fosite.OAuth2Provider
}

func (t *tokenHandler) TokenHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

// A JWT session allows to configure JWT
// header, body and claims for the *access token*.
// Sessions also keeps data between calls in a flow
// but the client credentials flow only uses the Token Endpoint
session := &oauth2.JWTSession{}

// NewAccessRequest creates an [Access Token Request](https://tools.ietf.org/html/rfc6749#section-4.1.3)
// if the given http request is valid.
ar, err := t.oauth.NewAccessRequest(ctx, r, session)
if err != nil {
t.oauth.WriteAccessError(w, ar, err)
return
}

// NewAccessResponse creates a [Access Token Response](https://tools.ietf.org/html/rfc6749#section-4.1.4)
// from a *Access Token Request*.
// This response has methods and attributes to setup a valid RFC response
// for Token Endpont, for example:
//
// ```
// {
// "access_token":"2YotnFZFEjr1zCsicMWpAA",
// "token_type":"example",
// "expires_in":3600,
// "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
// "example_parameter":"example_value"
// }
// ```
response, err := t.oauth.NewAccessResponse(ctx, ar)
if err != nil {
t.oauth.WriteAccessError(w, ar, err)
return
}

// WriteAccessResponse writes the Access Token Response
// as a HTTP response
t.oauth.WriteAccessResponse(w, ar, response)
}

```

`main.go`
```go
package main

import (
"crypto/rand"
"crypto/rsa"
"log"
"net/http"
"time"

"github.com/ory/fosite"
"github.com/ory/fosite/compose"
"github.com/ory/fosite/storage"
)

func main() {
// Generates a RSA key to sign JWT tokens
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatalf("Cannot generate RSA key: %v", err)
}

var storage = storage.NewMemoryStore()

// Register a test client in the memory store
storage.Clients["test-client"] = &fosite.DefaultClient{
ID: "test-client",
Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar"
GrantTypes: []string{"client_credentials"},
}

// check the api docs of compose.Config for further configuration options
var config = &compose.Config{
AccessTokenLifespan: time.Minute * 30,
}

var oauth2Provider = compose.Compose(
config,
storage,
compose.NewOAuth2JWTStrategy(
key,
// HMACStrategy is used to sign refresh token
// therefore not required for our example
nil,
),
// BCrypt hasher is automatically created when omitted.
// Hasher is used to store hashed client authentication passwords.
nil,
compose.OAuth2ClientCredentialsGrantFactory,
)

accessTokenHandler := tokenHandler{oauth: oauth2Provider}
http.HandleFunc("/token", accessTokenHandler.TokenHandler)
log.Println("serving on 0.0.0.0:8080")
if err := http.ListenAndServe("0.0.0.0:8080", nil); err != nil {
log.Fatal(err)
}
}

```

## To run

In one terminal run the http server as follows:

```bash
$go run .
2021/04/26 12:57:24 serving on 0.0.0.0:8080
```

In a different terminal issue a token as follows:

```bash
$curl http://localhost:8080/token -d grant_type=client_credentials -d client_id=test-client -d client_secret=foobar
{
"access_token": "<redacted>",
"expires_in": 1799,
"scope": "",
"token_type": "bearer"
}
```

0 comments on commit 893aae4

Please sign in to comment.