...

Source file src/github.com/ory/fosite/handler/oauth2/flow_refresh.go

Documentation: github.com/ory/fosite/handler/oauth2

     1  /*
     2   * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
    17   * @copyright 	2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
    18   * @license 	Apache-2.0
    19   *
    20   */
    21  
    22  package oauth2
    23  
    24  import (
    25  	"context"
    26  	"fmt"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/ory/x/errorsx"
    31  
    32  	"github.com/pkg/errors"
    33  
    34  	"github.com/ory/fosite"
    35  	"github.com/ory/fosite/storage"
    36  )
    37  
    38  type RefreshTokenGrantHandler struct {
    39  	AccessTokenStrategy    AccessTokenStrategy
    40  	RefreshTokenStrategy   RefreshTokenStrategy
    41  	TokenRevocationStorage TokenRevocationStorage
    42  
    43  	// AccessTokenLifespan defines the lifetime of an access token.
    44  	AccessTokenLifespan time.Duration
    45  
    46  	// RefreshTokenLifespan defines the lifetime of a refresh token.
    47  	RefreshTokenLifespan time.Duration
    48  
    49  	ScopeStrategy            fosite.ScopeStrategy
    50  	AudienceMatchingStrategy fosite.AudienceMatchingStrategy
    51  	RefreshTokenScopes       []string
    52  }
    53  
    54  // HandleTokenEndpointRequest implements https://tools.ietf.org/html/rfc6749#section-6
    55  func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error {
    56  	if !c.CanHandleTokenEndpointRequest(request) {
    57  		return errorsx.WithStack(fosite.ErrUnknownRequest)
    58  	}
    59  
    60  	if !request.GetClient().GetGrantTypes().Has("refresh_token") {
    61  		return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant 'refresh_token'."))
    62  	}
    63  
    64  	refresh := request.GetRequestForm().Get("refresh_token")
    65  	signature := c.RefreshTokenStrategy.RefreshTokenSignature(refresh)
    66  	originalRequest, err := c.TokenRevocationStorage.GetRefreshTokenSession(ctx, signature, request.GetSession())
    67  	if errors.Is(err, fosite.ErrInactiveToken) {
    68  		// Detected refresh token reuse
    69  		if rErr := c.handleRefreshTokenReuse(ctx, signature, originalRequest); rErr != nil {
    70  			return errorsx.WithStack(fosite.ErrServerError.WithWrap(rErr).WithDebug(rErr.Error()))
    71  		}
    72  
    73  		return errorsx.WithStack(fosite.ErrInactiveToken.WithWrap(err).WithDebug(err.Error()))
    74  	} else if errors.Is(err, fosite.ErrNotFound) {
    75  		return errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebugf("The refresh token has not been found: %s", err.Error()))
    76  	} else if err != nil {
    77  		return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
    78  	} else if err := c.RefreshTokenStrategy.ValidateRefreshToken(ctx, originalRequest, refresh); err != nil {
    79  		// The authorization server MUST ... validate the refresh token.
    80  		// This needs to happen after store retrieval for the session to be hydrated properly
    81  		if errors.Is(err, fosite.ErrTokenExpired) {
    82  			return errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error()))
    83  		}
    84  		return errorsx.WithStack(fosite.ErrInvalidRequest.WithWrap(err).WithDebug(err.Error()))
    85  	}
    86  
    87  	if !(len(c.RefreshTokenScopes) == 0 || originalRequest.GetGrantedScopes().HasOneOf(c.RefreshTokenScopes...)) {
    88  		scopeNames := strings.Join(c.RefreshTokenScopes, " or ")
    89  		hint := fmt.Sprintf("The OAuth 2.0 Client was not granted scope %s and may thus not perform the 'refresh_token' authorization grant.", scopeNames)
    90  		return errorsx.WithStack(fosite.ErrScopeNotGranted.WithHint(hint))
    91  
    92  	}
    93  
    94  	// The authorization server MUST ... and ensure that the refresh token was issued to the authenticated client
    95  	if originalRequest.GetClient().GetID() != request.GetClient().GetID() {
    96  		return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client ID from this request does not match the ID during the initial token issuance."))
    97  	}
    98  
    99  	request.SetSession(originalRequest.GetSession().Clone())
   100  	request.SetRequestedScopes(originalRequest.GetRequestedScopes())
   101  	request.SetRequestedAudience(originalRequest.GetRequestedAudience())
   102  
   103  	for _, scope := range originalRequest.GetGrantedScopes() {
   104  		if !c.ScopeStrategy(request.GetClient().GetScopes(), scope) {
   105  			return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope))
   106  		}
   107  		request.GrantScope(scope)
   108  	}
   109  
   110  	if err := c.AudienceMatchingStrategy(request.GetClient().GetAudience(), originalRequest.GetGrantedAudience()); err != nil {
   111  		return err
   112  	}
   113  
   114  	for _, audience := range originalRequest.GetGrantedAudience() {
   115  		request.GrantAudience(audience)
   116  	}
   117  
   118  	request.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().UTC().Add(c.AccessTokenLifespan).Round(time.Second))
   119  	if c.RefreshTokenLifespan > -1 {
   120  		request.GetSession().SetExpiresAt(fosite.RefreshToken, time.Now().UTC().Add(c.RefreshTokenLifespan).Round(time.Second))
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  // PopulateTokenEndpointResponse implements https://tools.ietf.org/html/rfc6749#section-6
   127  func (c *RefreshTokenGrantHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) (err error) {
   128  	if !c.CanHandleTokenEndpointRequest(requester) {
   129  		return errorsx.WithStack(fosite.ErrUnknownRequest)
   130  	}
   131  
   132  	accessToken, accessSignature, err := c.AccessTokenStrategy.GenerateAccessToken(ctx, requester)
   133  	if err != nil {
   134  		return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
   135  	}
   136  
   137  	refreshToken, refreshSignature, err := c.RefreshTokenStrategy.GenerateRefreshToken(ctx, requester)
   138  	if err != nil {
   139  		return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
   140  	}
   141  
   142  	signature := c.RefreshTokenStrategy.RefreshTokenSignature(requester.GetRequestForm().Get("refresh_token"))
   143  
   144  	ctx, err = storage.MaybeBeginTx(ctx, c.TokenRevocationStorage)
   145  	if err != nil {
   146  		return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
   147  	}
   148  	defer func() {
   149  		err = c.handleRefreshTokenEndpointStorageError(ctx, err)
   150  	}()
   151  
   152  	ts, err := c.TokenRevocationStorage.GetRefreshTokenSession(ctx, signature, nil)
   153  	if err != nil {
   154  		return err
   155  	} else if err := c.TokenRevocationStorage.RevokeAccessToken(ctx, ts.GetID()); err != nil {
   156  		return err
   157  	}
   158  
   159  	if err := c.TokenRevocationStorage.RevokeRefreshTokenMaybeGracePeriod(ctx, ts.GetID(), signature); err != nil {
   160  		return err
   161  	}
   162  
   163  	storeReq := requester.Sanitize([]string{})
   164  	storeReq.SetID(ts.GetID())
   165  
   166  	if err = c.TokenRevocationStorage.CreateAccessTokenSession(ctx, accessSignature, storeReq); err != nil {
   167  		return err
   168  	}
   169  
   170  	if err = c.TokenRevocationStorage.CreateRefreshTokenSession(ctx, refreshSignature, storeReq); err != nil {
   171  		return err
   172  	}
   173  
   174  	responder.SetAccessToken(accessToken)
   175  	responder.SetTokenType("bearer")
   176  	responder.SetExpiresIn(getExpiresIn(requester, fosite.AccessToken, c.AccessTokenLifespan, time.Now().UTC()))
   177  	responder.SetScopes(requester.GetGrantedScopes())
   178  	responder.SetExtra("refresh_token", refreshToken)
   179  
   180  	if err = storage.MaybeCommitTx(ctx, c.TokenRevocationStorage); err != nil {
   181  		return err
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // Reference: https://tools.ietf.org/html/rfc6819#section-5.2.2.3
   188  //
   189  //     The basic idea is to change the refresh token
   190  //     value with every refresh request in order to detect attempts to
   191  //     obtain access tokens using old refresh tokens.  Since the
   192  //     authorization server cannot determine whether the attacker or the
   193  //     legitimate client is trying to access, in case of such an access
   194  //     attempt the valid refresh token and the access authorization
   195  //     associated with it are both revoked.
   196  //
   197  func (c *RefreshTokenGrantHandler) handleRefreshTokenReuse(ctx context.Context, signature string, req fosite.Requester) (err error) {
   198  	ctx, err = storage.MaybeBeginTx(ctx, c.TokenRevocationStorage)
   199  	if err != nil {
   200  		return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
   201  	}
   202  	defer func() {
   203  		err = c.handleRefreshTokenEndpointStorageError(ctx, err)
   204  	}()
   205  
   206  	if err = c.TokenRevocationStorage.DeleteRefreshTokenSession(ctx, signature); err != nil {
   207  		return err
   208  	} else if err = c.TokenRevocationStorage.RevokeRefreshToken(
   209  		ctx, req.GetID(),
   210  	); err != nil && !errors.Is(err, fosite.ErrNotFound) {
   211  		return err
   212  	} else if err = c.TokenRevocationStorage.RevokeAccessToken(
   213  		ctx, req.GetID(),
   214  	); err != nil && !errors.Is(err, fosite.ErrNotFound) {
   215  		return err
   216  	}
   217  
   218  	if err = storage.MaybeCommitTx(ctx, c.TokenRevocationStorage); err != nil {
   219  		return err
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func (c *RefreshTokenGrantHandler) handleRefreshTokenEndpointStorageError(ctx context.Context, storageErr error) (err error) {
   226  	if storageErr == nil {
   227  		return nil
   228  	}
   229  
   230  	defer func() {
   231  		if rollBackTxnErr := storage.MaybeRollbackTx(ctx, c.TokenRevocationStorage); rollBackTxnErr != nil {
   232  			err = errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebugf("error: %s; rollback error: %s", err, rollBackTxnErr))
   233  		}
   234  	}()
   235  
   236  	if errors.Is(storageErr, fosite.ErrSerializationFailure) {
   237  		return errorsx.WithStack(fosite.ErrInvalidRequest.
   238  			WithDebugf(storageErr.Error()).
   239  			WithHint("Failed to refresh token because of multiple concurrent requests using the same token which is not allowed."))
   240  	}
   241  
   242  	if errors.Is(storageErr, fosite.ErrNotFound) || errors.Is(storageErr, fosite.ErrInactiveToken) {
   243  		return errorsx.WithStack(fosite.ErrInvalidRequest.
   244  			WithDebugf(storageErr.Error()).
   245  			WithHint("Failed to refresh token because of multiple concurrent requests using the same token which is not allowed."))
   246  	}
   247  
   248  	return errorsx.WithStack(fosite.ErrServerError.WithWrap(storageErr).WithDebug(storageErr.Error()))
   249  }
   250  
   251  func (c *RefreshTokenGrantHandler) CanSkipClientAuth(requester fosite.AccessRequester) bool {
   252  	return false
   253  }
   254  
   255  func (c *RefreshTokenGrantHandler) CanHandleTokenEndpointRequest(requester fosite.AccessRequester) bool {
   256  	// grant_type REQUIRED.
   257  	// Value MUST be set to "refresh_token".
   258  	return requester.GetGrantTypes().ExactOne("refresh_token")
   259  }
   260  

View as plain text