Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handler/oauth2: use hmac strategy for jwt refresh tokens #190

Merged
merged 2 commits into from
Jul 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ bumps (`0.1.0` -> `0.2.0`).
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [0.11.0](#0110)
- [WildcardScopeStrategy](#wildcardscopestrategy)
- [Refresh tokens and authorize codes are no longer JWTs](#refresh-tokens-and-authorize-codes-are-no-longer-jwts)
- [Delete access tokens when persisting refresh session](#delete-access-tokens-when-persisting-refresh-session)
- [0.10.0](#0100)
- [0.9.0](#090)
- [0.8.0](#080)
- [Breaking changes](#breaking-changes)
Expand All @@ -21,6 +27,10 @@ bumps (`0.1.0` -> `0.2.0`).

## 0.11.0

This release introduces breaking changes:

### WildcardScopeStrategy

A new [scope strategy](https://github.com/ory/fosite/pull/187) was introduced called `WildcardScopeStrategy`. This strategy is now the default when using
the composer. To set the HierarchicScopeStrategy strategy, do:

Expand All @@ -32,7 +42,47 @@ var config = &compose.Config{
}
```

### Refresh tokens and authorize codes are no longer JWTs

Using JWTs for refresh tokens and authorize codes did not make sense:

1. Refresh tokens are long-living credentials, JWTs require an expiry date.
2. Refresh tokens are never validated client-side, only server-side. Thus access to the store is available.
3. Authorize codes are never validated client-side, only server-side.

Also, one compose method changed due to this:

```go
package compose

// ..

- func NewOAuth2JWTStrategy(key *rsa.PrivateKey) *oauth2.RS256JWTStrategy
+ func NewOAuth2JWTStrategy(key *rsa.PrivateKey, strategy *oauth2.HMACSHAStrategy) *oauth2.RS256JWTStrategy
```

### Delete access tokens when persisting refresh session

Please delete access tokens in your store when you persist a refresh session. This increases security. Here
is an example of how to do that using only existing methods:

```go
func (s *MemoryStore) PersistRefreshTokenGrantSession(ctx context.Context, originalRefreshSignature, accessSignature, refreshSignature string, request fosite.Requester) error {
if ts, err := s.GetRefreshTokenSession(ctx, originalRefreshSignature, nil); err != nil {
return err
} else if err := s.RevokeAccessToken(ctx, ts.GetID()); err != nil {
return err
} else if err := s.RevokeRefreshToken(ctx, ts.GetID()); err != nil {
return err
} else if err := s.CreateAccessTokenSession(ctx, accessSignature, request); err != nil {
return err
} else if err := s.CreateRefreshTokenSession(ctx, refreshSignature, request); err != nil {
return err
}

return nil
}
```

## 0.10.0

Expand Down
3 changes: 2 additions & 1 deletion compose/compose_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ func NewOAuth2HMACStrategy(config *Config, secret []byte) *oauth2.HMACSHAStrateg
}
}

func NewOAuth2JWTStrategy(key *rsa.PrivateKey) *oauth2.RS256JWTStrategy {
func NewOAuth2JWTStrategy(key *rsa.PrivateKey, strategy *oauth2.HMACSHAStrategy) *oauth2.RS256JWTStrategy {
return &oauth2.RS256JWTStrategy{
RS256JWTStrategy: &jwt.RS256JWTStrategy{
PrivateKey: key,
},
HMACSHAStrategy: strategy,
}
}

Expand Down
39 changes: 19 additions & 20 deletions handler/oauth2/strategy_jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import (
// RS256JWTStrategy is a JWT RS256 strategy.
type RS256JWTStrategy struct {
*jwt.RS256JWTStrategy
Issuer string
HMACSHAStrategy *HMACSHAStrategy
Issuer string
}

func (h RS256JWTStrategy) signature(token string) string {
Expand All @@ -31,12 +32,13 @@ func (h RS256JWTStrategy) AccessTokenSignature(token string) string {
return h.signature(token)
}

func (h RS256JWTStrategy) RefreshTokenSignature(token string) string {
return h.signature(token)
func (h *RS256JWTStrategy) GenerateAccessToken(_ context.Context, requester fosite.Requester) (token string, signature string, err error) {
return h.generate(fosite.AccessToken, requester)
}

func (h RS256JWTStrategy) AuthorizeCodeSignature(token string) string {
return h.signature(token)
func (h *RS256JWTStrategy) ValidateAccessToken(_ context.Context, _ fosite.Requester, token string) error {
_, err := h.validate(token)
return err
}

func (h *RS256JWTStrategy) ValidateJWT(tokenType fosite.TokenType, token string) (requester fosite.Requester, err error) {
Expand Down Expand Up @@ -68,31 +70,28 @@ func (h *RS256JWTStrategy) ValidateJWT(tokenType fosite.TokenType, token string)
return
}

func (h *RS256JWTStrategy) GenerateAccessToken(_ context.Context, requester fosite.Requester) (token string, signature string, err error) {
return h.generate(fosite.AccessToken, requester)
func (h RS256JWTStrategy) RefreshTokenSignature(token string) string {
return h.HMACSHAStrategy.RefreshTokenSignature(token)
}

func (h *RS256JWTStrategy) ValidateAccessToken(_ context.Context, _ fosite.Requester, token string) error {
_, err := h.validate(token)
return err
func (h RS256JWTStrategy) AuthorizeCodeSignature(token string) string {
return h.HMACSHAStrategy.AuthorizeCodeSignature(token)
}

func (h *RS256JWTStrategy) GenerateRefreshToken(_ context.Context, requester fosite.Requester) (token string, signature string, err error) {
return h.generate(fosite.RefreshToken, requester)
func (h *RS256JWTStrategy) GenerateRefreshToken(ctx context.Context, req fosite.Requester) (token string, signature string, err error) {
return h.HMACSHAStrategy.GenerateRefreshToken(ctx, req)
}

func (h *RS256JWTStrategy) ValidateRefreshToken(_ context.Context, _ fosite.Requester, token string) error {
_, err := h.validate(token)
return err
func (h *RS256JWTStrategy) ValidateRefreshToken(ctx context.Context, req fosite.Requester, token string) error {
return h.HMACSHAStrategy.ValidateRefreshToken(ctx, req, token)
}

func (h *RS256JWTStrategy) GenerateAuthorizeCode(_ context.Context, requester fosite.Requester) (token string, signature string, err error) {
return h.generate(fosite.AuthorizeCode, requester)
func (h *RS256JWTStrategy) GenerateAuthorizeCode(ctx context.Context, req fosite.Requester) (token string, signature string, err error) {
return h.HMACSHAStrategy.GenerateAuthorizeCode(ctx, req)
}

func (h *RS256JWTStrategy) ValidateAuthorizeCode(_ context.Context, requester fosite.Requester, token string) error {
_, err := h.validate(token)
return err
func (h *RS256JWTStrategy) ValidateAuthorizeCode(ctx context.Context, req fosite.Requester, token string) error {
return h.HMACSHAStrategy.ValidateAuthorizeCode(ctx, req, token)
}

func (h *RS256JWTStrategy) validate(token string) (t *jwtx.Token, err error) {
Expand Down
40 changes: 0 additions & 40 deletions handler/oauth2/strategy_jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,43 +99,3 @@ func TestAccessToken(t *testing.T) {
}
}
}

func TestRefreshToken(t *testing.T) {
token, signature, err := j.GenerateRefreshToken(nil, jwtValidCase(fosite.RefreshToken))
assert.Nil(t, err, "%s", err)
assert.Equal(t, strings.Split(token, ".")[2], signature)

validate := j.signature(token)
err = j.ValidateRefreshToken(nil, jwtValidCase(fosite.RefreshToken), token)
assert.Nil(t, err, "%s", err)
assert.Equal(t, signature, validate)
}

func TestGenerateAuthorizeCode(t *testing.T) {
for _, c := range []struct {
r *fosite.Request
pass bool
}{
{
r: jwtValidCase(fosite.AuthorizeCode),
pass: true,
},
{
r: jwtExpiredCase(fosite.AuthorizeCode),
pass: false,
},
} {
token, signature, err := j.GenerateAuthorizeCode(nil, c.r)
assert.Nil(t, err, "%s", err)
assert.Equal(t, strings.Split(token, ".")[2], signature)

validate := j.signature(token)
err = j.ValidateAuthorizeCode(nil, c.r, token)
if c.pass {
assert.Nil(t, err, "%s", err)
assert.Equal(t, signature, validate)
} else {
assert.NotNil(t, err, "%s", err)
}
}
}
1 change: 1 addition & 0 deletions integration/helper_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ var jwtStrategy = &oauth2.RS256JWTStrategy{
RS256JWTStrategy: &jwt.RS256JWTStrategy{
PrivateKey: internal.MustRSAKey(),
},
HMACSHAStrategy: hmacStrategy,
}

func mockServer(t *testing.T, f fosite.OAuth2Provider, session fosite.Session) *httptest.Server {
Expand Down