Skip to content

Commit f0386ed

Browse files
authored
Feature/request object (#38)
1 parent 9f4a67d commit f0386ed

File tree

12 files changed

+273
-65
lines changed

12 files changed

+273
-65
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ The available flags are:
7474
--password string resource owner password credentials grant flow password
7575
--pkce enable proof key for code exchange (PKCE)
7676
--refresh-token string refresh token
77+
--request-object pass request parameters as jwt
7778
--response-mode string response mode
7879
--response-types strings response type
7980
--scopes strings requested scopes

cmd/log.go

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import (
77
"crypto/x509"
88
"encoding/json"
99
"encoding/pem"
10-
"github.com/go-jose/go-jose/v3/jwt"
1110
"strconv"
1211
"strings"
1312

13+
"github.com/go-jose/go-jose/v3/jwt"
14+
1415
"github.com/cloudentity/oauth2c/internal/oauth2"
1516
"github.com/grantae/certinfo"
1617
"github.com/pterm/pterm"
@@ -274,6 +275,39 @@ func LogJARM(request oauth2.Request) {
274275
}
275276
}
276277

278+
func LogRequestObject(r oauth2.Request) {
279+
var (
280+
request = r.URL.Query().Get("request")
281+
requestClaims map[string]interface{}
282+
token *jwt.JSONWebToken
283+
err error
284+
)
285+
286+
if silent {
287+
return
288+
}
289+
290+
if request != "" {
291+
if token, requestClaims, err = oauth2.UnsafeParseJWT(request); err != nil {
292+
pterm.Error.Println(err)
293+
} else {
294+
pterm.DefaultBox.WithTitle("Request object").Printfln("request = JWT-%s(payload)", token.Headers[0].Algorithm)
295+
pterm.Println()
296+
pterm.Println("Payload")
297+
LogJson(requestClaims)
298+
pterm.Println()
299+
300+
if r.SigningKey != nil {
301+
LogKey("Signing key", r.SigningKey)
302+
}
303+
304+
if r.EncryptionKey != nil {
305+
LogKey("Encryption key", r.EncryptionKey)
306+
}
307+
}
308+
}
309+
}
310+
277311
func LogAssertion(request oauth2.Request, title string, name string) {
278312
var (
279313
assertion = request.Form.Get(name)
@@ -301,8 +335,15 @@ func LogAssertion(request oauth2.Request, title string, name string) {
301335
LogJson(claims)
302336
pterm.Println("")
303337

304-
pterm.Println("Key")
305-
switch key := request.Key.(type) {
338+
LogKey("Signing key", request.SigningKey)
339+
}
340+
341+
func LogKey(name string, key interface{}) {
342+
var err error
343+
344+
pterm.Println(name)
345+
346+
switch key := key.(type) {
306347
case *rsa.PrivateKey:
307348
p := bytes.Buffer{}
308349

@@ -333,6 +374,8 @@ func LogAssertion(request oauth2.Request, title string, name string) {
333374
pterm.FgGray.Printfln(p.String())
334375
case []byte:
335376
pterm.FgGray.Println(string(key))
377+
case string:
378+
pterm.FgGray.Println(key)
336379
}
337380

338381
pterm.Println()
@@ -369,5 +412,7 @@ func LogSubjectTokenAndActorToken(request oauth2.Request) {
369412
}
370413
}
371414

372-
pterm.Println()
415+
if subjectToken != "" || actorToken != "" {
416+
pterm.Println()
417+
}
373418
}

cmd/oauth2.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func NewOAuth2Cmd() (cmd *OAuth2Cmd) {
5757
cmd.PersistentFlags().StringSliceVar(&cconfig.Scopes, "scopes", []string{}, "requested scopes")
5858
cmd.PersistentFlags().BoolVar(&cconfig.PKCE, "pkce", false, "enable proof key for code exchange (PKCE)")
5959
cmd.PersistentFlags().BoolVar(&cconfig.PAR, "par", false, "enable pushed authorization requests (PAR)")
60+
cmd.PersistentFlags().BoolVar(&cconfig.RequestObject, "request-object", false, "pass request parameters as jwt")
6061
cmd.PersistentFlags().StringVar(&cconfig.Assertion, "assertion", "", "claims for jwt bearer assertion")
6162
cmd.PersistentFlags().StringVar(&cconfig.SigningKey, "signing-key", "", "path or url to signing key in jwks format")
6263
cmd.PersistentFlags().StringVar(&cconfig.EncryptionKey, "encryption-key", "", "path or url to encryption key in jwks format")

cmd/oauth2_authorize_code.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ func (c *OAuth2Cmd) AuthorizationCodeGrantFlow(clientConfig oauth2.ClientConfig,
3636

3737
LogSection("Request authorization")
3838

39+
LogRequestObject(parRequest)
3940
LogRequest(authorizeRequest)
4041
} else {
4142
LogSection("Request authorization")
4243

43-
if authorizeRequest, codeVerifier, err = oauth2.RequestAuthorization(addr, clientConfig, serverConfig); err != nil {
44+
if authorizeRequest, codeVerifier, err = oauth2.RequestAuthorization(addr, clientConfig, serverConfig, hc); err != nil {
4445
return err
4546
}
4647

48+
LogRequestObject(authorizeRequest)
4749
LogRequest(authorizeRequest)
4850
}
4951

cmd/oauth2_implicit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func (c *OAuth2Cmd) ImplicitGrantFlow(clientConfig oauth2.ClientConfig, serverCo
1919
// authorize endpoint
2020
LogSection("Request authorization")
2121

22-
if authorizeRequest, _, err = oauth2.RequestAuthorization(addr, clientConfig, serverConfig); err != nil {
22+
if authorizeRequest, _, err = oauth2.RequestAuthorization(addr, clientConfig, serverConfig, hc); err != nil {
2323
return err
2424
}
2525

docs/examples.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,28 @@ oauth2c https://oauth2c.us.authz.cloudentity.io/oauth2c/demo \
392392

393393
## Extensions
394394

395+
### Request Object
396+
397+
The Request Object is a JWT that contains the parameters of an authorization request. It allows the request to be passed along as a single,
398+
self-contained parameter, and it can be optionally signed and/or encrypted for added security.
399+
400+
<details>
401+
<summary>Show example</summary>
402+
403+
``` sh
404+
oauth2c https://oauth2c.us.authz.cloudentity.io/oauth2c/demo \
405+
--client-id cauktionbud6q8ftlqq0 \
406+
--client-secret HCwQ5uuUWBRHd04ivjX5Kl0Rz8zxMOekeLtqzki0GPc \
407+
--response-types code \
408+
--response-mode query \
409+
--grant-type authorization_code \
410+
--auth-method client_secret_basic \
411+
--scopes openid,email,offline_access \
412+
--request-object
413+
```
414+
415+
</details>
416+
395417
### PKCE
396418

397419
The Proof Key for Code Exchange (PKCE) is an extension to the OAuth2 authorization code grant flow that

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.19
44

55
require (
66
github.com/go-jose/go-jose/v3 v3.0.0
7-
github.com/golang-jwt/jwt v3.2.2+incompatible
7+
github.com/golang-jwt/jwt/v4 v4.4.3
88
github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b
99
github.com/hashicorp/go-multierror v1.1.1
1010
github.com/imdario/mergo v0.3.13

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
1919
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2020
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
2121
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
22-
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
23-
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
22+
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
23+
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
2424
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
2525
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
2626
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=

internal/oauth2/jwe.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package oauth2
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-jose/go-jose/v3"
7+
"github.com/pkg/errors"
8+
)
9+
10+
type EncrypterProvider func() (jose.Encrypter, interface{}, error)
11+
12+
func JWEEncrypter(clientConfig ClientConfig, hc *http.Client) EncrypterProvider {
13+
return func() (encrypter jose.Encrypter, _ interface{}, err error) {
14+
var key jose.JSONWebKey
15+
16+
if clientConfig.EncryptionKey == "" {
17+
return nil, nil, errors.New("no encryption key path")
18+
}
19+
20+
if key, err = ReadKey(EncryptionKey, clientConfig.EncryptionKey, hc); err != nil {
21+
return nil, nil, errors.Wrapf(err, "failed to read encryption key from %s", clientConfig.EncryptionKey)
22+
}
23+
24+
if encrypter, err = jose.NewEncrypter(
25+
jose.A256GCM,
26+
jose.Recipient{
27+
Algorithm: jose.KeyAlgorithm(key.Algorithm),
28+
Key: key.Key,
29+
},
30+
(&jose.EncrypterOptions{}).WithType("JWT").WithContentType("JWT"),
31+
); err != nil {
32+
return nil, nil, errors.Wrapf(err, "failed to create an encrypter")
33+
}
34+
35+
return encrypter, key.Key, nil
36+
}
37+
}
38+
39+
func EncryptJWT(token string, encrypterProvider EncrypterProvider) (nestedJWT string, key interface{}, err error) {
40+
var (
41+
encrypter jose.Encrypter
42+
jwe *jose.JSONWebEncryption
43+
)
44+
45+
if encrypter, key, err = encrypterProvider(); err != nil {
46+
return "", nil, err
47+
}
48+
49+
if jwe, err = encrypter.Encrypt([]byte(token)); err != nil {
50+
return "", nil, err
51+
}
52+
53+
if nestedJWT, err = jwe.CompactSerialize(); err != nil {
54+
return "", nil, err
55+
}
56+
57+
return nestedJWT, key, nil
58+
}

internal/oauth2/jwt.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package oauth2
33
import (
44
"encoding/json"
55
"net/http"
6+
"net/url"
67
"time"
78

89
"github.com/go-jose/go-jose/v3"
910
"github.com/go-jose/go-jose/v3/jwt"
11+
golangjwt "github.com/golang-jwt/jwt/v4"
1012
"github.com/pkg/errors"
1113
)
1214

@@ -92,6 +94,25 @@ func AssertionClaims(serverConfig ServerConfig, clientConfig ClientConfig) Claim
9294
}
9395
}
9496

97+
func RequestObjectClaims(params url.Values, serverConfig ServerConfig, clientConfig ClientConfig) ClaimsProvider {
98+
return func() (map[string]interface{}, error) {
99+
claims := map[string]interface{}{
100+
"iss": clientConfig.ClientID,
101+
"aud": serverConfig.Issuer,
102+
}
103+
104+
for key, values := range params {
105+
if len(values) == 0 {
106+
continue
107+
}
108+
109+
claims[key] = values[0]
110+
}
111+
112+
return claims, nil
113+
}
114+
}
115+
95116
func ClientAssertionClaims(serverConfig ServerConfig, clientConfig ClientConfig) ClaimsProvider {
96117
return func() (map[string]interface{}, error) {
97118
return map[string]interface{}{
@@ -135,3 +156,22 @@ func SignJWT(claimsProvider ClaimsProvider, signerProvider SignerProvider) (jwt
135156

136157
return jwt, key, nil
137158
}
159+
160+
func PlaintextJWT(claimsProvider ClaimsProvider) (jwt string, key string, err error) {
161+
var (
162+
claims map[string]interface{}
163+
t *golangjwt.Token
164+
)
165+
166+
if claims, err = claimsProvider(); err != nil {
167+
return "", "", errors.Wrapf(err, "failed to build claims")
168+
}
169+
170+
t = golangjwt.NewWithClaims(golangjwt.SigningMethodNone, golangjwt.MapClaims(claims))
171+
172+
if jwt, err = t.SignedString(golangjwt.UnsafeAllowNoneSignatureType); err != nil {
173+
return "", "", err
174+
}
175+
176+
return jwt, "none", nil
177+
}

0 commit comments

Comments
 (0)