...

Source file src/github.com/ory/fosite/handler/rfc7523/handler.go

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

     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 rfc7523
    23  
    24  import (
    25  	"context"
    26  	"time"
    27  
    28  	"github.com/ory/fosite/handler/oauth2"
    29  
    30  	"gopkg.in/square/go-jose.v2"
    31  	"gopkg.in/square/go-jose.v2/jwt"
    32  
    33  	"github.com/ory/fosite"
    34  	"github.com/ory/x/errorsx"
    35  )
    36  
    37  const grantTypeJWTBearer = "urn:ietf:params:oauth:grant-type:jwt-bearer"
    38  
    39  type Handler struct {
    40  	Storage                  RFC7523KeyStorage
    41  	ScopeStrategy            fosite.ScopeStrategy
    42  	AudienceMatchingStrategy fosite.AudienceMatchingStrategy
    43  
    44  	// TokenURL is the the URL of the Authorization Server's Token Endpoint.
    45  	TokenURL string
    46  	// SkipClientAuth indicates, if client authentication can be skipped.
    47  	SkipClientAuth bool
    48  	// JWTIDOptional indicates, if jti (JWT ID) claim required or not.
    49  	JWTIDOptional bool
    50  	// JWTIssuedDateOptional indicates, if "iat" (issued at) claim required or not.
    51  	JWTIssuedDateOptional bool
    52  	// JWTMaxDuration sets the maximum time after token issued date (if present), during which the token is
    53  	// considered valid. If "iat" claim is not present, then current time will be used as issued date.
    54  	JWTMaxDuration time.Duration
    55  
    56  	*oauth2.HandleHelper
    57  }
    58  
    59  // HandleTokenEndpointRequest implements https://tools.ietf.org/html/rfc6749#section-4.1.3 (everything) and
    60  // https://tools.ietf.org/html/rfc7523#section-2.1 (everything)
    61  func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error {
    62  	if err := c.CheckRequest(request); err != nil {
    63  		return err
    64  	}
    65  
    66  	assertion := request.GetRequestForm().Get("assertion")
    67  	if assertion == "" {
    68  		return errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("The assertion request parameter must be set when using grant_type of '%s'.", grantTypeJWTBearer))
    69  	}
    70  
    71  	token, err := jwt.ParseSigned(assertion)
    72  	if err != nil {
    73  		return errorsx.WithStack(fosite.ErrInvalidGrant.
    74  			WithHint("Unable to parse JSON Web Token passed in \"assertion\" request parameter.").
    75  			WithWrap(err).WithDebug(err.Error()),
    76  		)
    77  	}
    78  
    79  	// Check fo required claims in token, so we can later find public key based on them.
    80  	if err := c.validateTokenPreRequisites(token); err != nil {
    81  		return err
    82  	}
    83  
    84  	key, err := c.findPublicKeyForToken(ctx, token)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	claims := jwt.Claims{}
    90  	if err := token.Claims(key, &claims); err != nil {
    91  		return errorsx.WithStack(fosite.ErrInvalidGrant.
    92  			WithHint("Unable to verify the integrity of the 'assertion' value.").
    93  			WithWrap(err).WithDebug(err.Error()),
    94  		)
    95  	}
    96  
    97  	if err := c.validateTokenClaims(ctx, claims, key); err != nil {
    98  		return err
    99  	}
   100  
   101  	scopes, err := c.Storage.GetPublicKeyScopes(ctx, claims.Issuer, claims.Subject, key.KeyID)
   102  	if err != nil {
   103  		return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
   104  	}
   105  
   106  	for _, scope := range request.GetRequestedScopes() {
   107  		if !c.ScopeStrategy(scopes, scope) {
   108  			return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The public key registered for issuer \"%s\" and subject \"%s\" is not allowed to request scope \"%s\".", claims.Issuer, claims.Subject, scope))
   109  		}
   110  	}
   111  
   112  	if claims.ID != "" {
   113  		if err := c.Storage.MarkJWTUsedForTime(ctx, claims.ID, claims.Expiry.Time()); err != nil {
   114  			return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
   115  		}
   116  	}
   117  
   118  	for _, scope := range request.GetRequestedScopes() {
   119  		request.GrantScope(scope)
   120  	}
   121  
   122  	for _, audience := range claims.Audience {
   123  		request.GrantAudience(audience)
   124  	}
   125  
   126  	session, err := c.getSessionFromRequest(request)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	session.SetExpiresAt(fosite.AccessToken, time.Now().UTC().Add(c.HandleHelper.AccessTokenLifespan).Round(time.Second))
   131  	session.SetSubject(claims.Subject)
   132  
   133  	return nil
   134  }
   135  
   136  func (c *Handler) PopulateTokenEndpointResponse(ctx context.Context, request fosite.AccessRequester, response fosite.AccessResponder) error {
   137  	if err := c.CheckRequest(request); err != nil {
   138  		return err
   139  	}
   140  
   141  	return c.IssueAccessToken(ctx, request, response)
   142  }
   143  
   144  func (c *Handler) CanSkipClientAuth(requester fosite.AccessRequester) bool {
   145  	return c.SkipClientAuth
   146  }
   147  
   148  func (c *Handler) CanHandleTokenEndpointRequest(requester fosite.AccessRequester) bool {
   149  	// grant_type REQUIRED.
   150  	// Value MUST be set to "authorization_code"
   151  	return requester.GetGrantTypes().ExactOne(grantTypeJWTBearer)
   152  }
   153  
   154  func (c *Handler) CheckRequest(request fosite.AccessRequester) error {
   155  	if !c.CanHandleTokenEndpointRequest(request) {
   156  		return errorsx.WithStack(fosite.ErrUnknownRequest)
   157  	}
   158  
   159  	// Client Authentication is optional:
   160  	//
   161  	// Authentication of the client is optional, as described in
   162  	//   Section 3.2.1 of OAuth 2.0 [RFC6749] and consequently, the
   163  	//   "client_id" is only needed when a form of client authentication that
   164  	//   relies on the parameter is used.
   165  
   166  	// if client is authenticated, check grant types
   167  	if !c.CanSkipClientAuth(request) && !request.GetClient().GetGrantTypes().Has(grantTypeJWTBearer) {
   168  		return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHintf("The OAuth 2.0 Client is not allowed to use authorization grant \"%s\".", grantTypeJWTBearer))
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  func (c *Handler) validateTokenPreRequisites(token *jwt.JSONWebToken) error {
   175  	unverifiedClaims := jwt.Claims{}
   176  	if err := token.UnsafeClaimsWithoutVerification(&unverifiedClaims); err != nil {
   177  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   178  			WithHint("Looks like there are no claims in JWT in \"assertion\" request parameter.").
   179  			WithWrap(err).WithDebug(err.Error()),
   180  		)
   181  	}
   182  	if unverifiedClaims.Issuer == "" {
   183  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   184  			WithHint("The JWT in \"assertion\" request parameter MUST contain an \"iss\" (issuer) claim."),
   185  		)
   186  	}
   187  	if unverifiedClaims.Subject == "" {
   188  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   189  			WithHint("The JWT in \"assertion\" request parameter MUST contain a \"sub\" (subject) claim."),
   190  		)
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  func (c *Handler) findPublicKeyForToken(ctx context.Context, token *jwt.JSONWebToken) (*jose.JSONWebKey, error) {
   197  	unverifiedClaims := jwt.Claims{}
   198  	if err := token.UnsafeClaimsWithoutVerification(&unverifiedClaims); err != nil {
   199  		return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithWrap(err).WithDebug(err.Error()))
   200  	}
   201  
   202  	var keyID string
   203  	for _, header := range token.Headers {
   204  		if header.KeyID != "" {
   205  			keyID = header.KeyID
   206  			break
   207  		}
   208  	}
   209  
   210  	keyNotFoundErr := fosite.ErrInvalidGrant.WithHintf(
   211  		"No public JWK was registered for issuer \"%s\" and subject \"%s\", and public key is required to check signature of JWT in \"assertion\" request parameter.",
   212  		unverifiedClaims.Issuer,
   213  		unverifiedClaims.Subject,
   214  	)
   215  	if keyID != "" {
   216  		key, err := c.Storage.GetPublicKey(ctx, unverifiedClaims.Issuer, unverifiedClaims.Subject, keyID)
   217  		if err != nil {
   218  			return nil, errorsx.WithStack(keyNotFoundErr.WithWrap(err).WithDebug(err.Error()))
   219  		}
   220  		return key, nil
   221  	}
   222  
   223  	keys, err := c.Storage.GetPublicKeys(ctx, unverifiedClaims.Issuer, unverifiedClaims.Subject)
   224  	if err != nil {
   225  		return nil, errorsx.WithStack(keyNotFoundErr.WithWrap(err).WithDebug(err.Error()))
   226  	}
   227  
   228  	claims := jwt.Claims{}
   229  	for _, key := range keys.Keys {
   230  		err := token.Claims(key, &claims)
   231  		if err == nil {
   232  			return &key, nil
   233  		}
   234  	}
   235  
   236  	return nil, errorsx.WithStack(keyNotFoundErr)
   237  }
   238  
   239  func (c *Handler) validateTokenClaims(ctx context.Context, claims jwt.Claims, key *jose.JSONWebKey) error {
   240  	if len(claims.Audience) == 0 {
   241  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   242  			WithHint("The JWT in \"assertion\" request parameter MUST contain an \"aud\" (audience) claim."),
   243  		)
   244  	}
   245  
   246  	if !claims.Audience.Contains(c.TokenURL) {
   247  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   248  			WithHintf(
   249  				"The JWT in \"assertion\" request parameter MUST contain an \"aud\" (audience) claim containing a value \"%s\" that identifies the authorization server as an intended audience.",
   250  				c.TokenURL,
   251  			),
   252  		)
   253  	}
   254  
   255  	if claims.Expiry == nil {
   256  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   257  			WithHint("The JWT in \"assertion\" request parameter MUST contain an \"exp\" (expiration time) claim."),
   258  		)
   259  	}
   260  
   261  	if claims.Expiry.Time().Before(time.Now()) {
   262  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   263  			WithHint("The JWT in \"assertion\" request parameter expired."),
   264  		)
   265  	}
   266  
   267  	if claims.NotBefore != nil && !claims.NotBefore.Time().Before(time.Now()) {
   268  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   269  			WithHintf(
   270  				"The JWT in \"assertion\" request parameter contains an \"nbf\" (not before) claim, that identifies the time '%s' before which the token MUST NOT be accepted.",
   271  				claims.NotBefore.Time().Format(time.RFC3339),
   272  			),
   273  		)
   274  	}
   275  
   276  	if !c.JWTIssuedDateOptional && claims.IssuedAt == nil {
   277  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   278  			WithHint("The JWT in \"assertion\" request parameter MUST contain an \"iat\" (issued at) claim."),
   279  		)
   280  	}
   281  
   282  	var issuedDate time.Time
   283  	if claims.IssuedAt != nil {
   284  		issuedDate = claims.IssuedAt.Time()
   285  	} else {
   286  		issuedDate = time.Now()
   287  	}
   288  	if claims.Expiry.Time().Sub(issuedDate) > c.JWTMaxDuration {
   289  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   290  			WithHintf(
   291  				"The JWT in \"assertion\" request parameter contains an \"exp\" (expiration time) claim with value \"%s\" that is unreasonably far in the future, considering token issued at \"%s\".",
   292  				claims.Expiry.Time().Format(time.RFC3339),
   293  				issuedDate.Format(time.RFC3339),
   294  			),
   295  		)
   296  	}
   297  
   298  	if !c.JWTIDOptional && claims.ID == "" {
   299  		return errorsx.WithStack(fosite.ErrInvalidGrant.
   300  			WithHint("The JWT in \"assertion\" request parameter MUST contain an \"jti\" (JWT ID) claim."),
   301  		)
   302  	}
   303  
   304  	if claims.ID != "" {
   305  		used, err := c.Storage.IsJWTUsed(ctx, claims.ID)
   306  		if err != nil {
   307  			return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
   308  		}
   309  		if used {
   310  			return errorsx.WithStack(fosite.ErrJTIKnown)
   311  		}
   312  	}
   313  
   314  	return nil
   315  }
   316  
   317  type extendedSession interface {
   318  	Session
   319  	fosite.Session
   320  }
   321  
   322  func (c *Handler) getSessionFromRequest(requester fosite.AccessRequester) (extendedSession, error) {
   323  	session := requester.GetSession()
   324  	if jwtSession, ok := session.(extendedSession); !ok {
   325  		return nil, errorsx.WithStack(
   326  			fosite.ErrServerError.WithHintf("Session must be of type *rfc7523.Session but got type: %T", session),
   327  		)
   328  	} else {
   329  		return jwtSession, nil
   330  	}
   331  }
   332  

View as plain text