Skip to content

Commit

Permalink
feat(oidc): server issuer identification (#5912)
Browse files Browse the repository at this point in the history
This implements RFC9207 OAuth 2.0 Authorization Server Issuer Identification which is part of the FAPI 2.0 security profile as per https://datatracker.ietf.org/doc/html/rfc9207 and related specifications. 

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
  • Loading branch information
james-d-elliott committed Aug 30, 2023
1 parent 4f1ee01 commit 0da4abf
Show file tree
Hide file tree
Showing 17 changed files with 290 additions and 42 deletions.
18 changes: 13 additions & 5 deletions docs/content/en/integration/openid-connect/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,21 @@ configuration. The value field is both the required value for the `response_mode
and the [response_modes](../../configuration/identity-providers/openid-connect/clients.md#responsemodes) client
configuration option.

| Name | Value |
|:---------------------:|:-----------:|
| [OAuth 2.0 Form Post] | `form_post` |
| Query String | `query` |
| Fragment | `fragment` |
| Name | Supported | Value |
|:---------------------:|:---------:|:---------------:|
| [OAuth 2.0 Form Post] | Yes | `form_post` |
| Query String | Yes | `query` |
| Fragment | Yes | `fragment` |
| [JARM] | No | `jwt` |
| [Form Post (JARM)] | No | `form_post.jwt` |
| [Query String (JARM)] | No | `query.jwt` |
| [Fragment (JARM)] | No | `fragment.jwt` |

[OAuth 2.0 Form Post]: https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
[Form Post (JARM)]: https://openid.net/specs/openid-financial-api-jarm.html#response-mode-form_post.jwt
[Query String (JARM)]: https://openid.net/specs/openid-financial-api-jarm.html#response-mode-query.jwt
[Fragment (JARM)]: https://openid.net/specs/openid-financial-api-jarm.html#response-mode-fragment.jwt
[JARM]: https://openid.net/specs/openid-financial-api-jarm.html#response-mode-jwt

### Grant Types

Expand Down
10 changes: 6 additions & 4 deletions internal/handlers/handler_oidc_authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
return
}

issuer = ctx.RootURL()

var (
userSession session.UserSession
consent *model.OAuth2ConsentSession
Expand All @@ -102,6 +100,8 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
return
}

issuer = ctx.RootURL()

if consent, handled = handleOIDCAuthorizationConsent(ctx, issuer, client, userSession, rw, r, requester); handled {
return
}
Expand Down Expand Up @@ -145,6 +145,8 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
ctx.SetUserValue(middlewares.UserValueKeyOpenIDConnectResponseModeFormPost, true)
}

responder.GetParameters().Set(oidc.FormParameterIssuer, issuer.String())

ctx.Providers.OpenIDConnect.WriteAuthorizeResponse(ctx, rw, requester, responder)
}

Expand Down Expand Up @@ -185,15 +187,15 @@ func OpenIDConnectPushedAuthorizationRequest(ctx *middlewares.AutheliaCtx, rw ht
if err = client.ValidatePKCEPolicy(requester); err != nil {
ctx.Logger.Errorf("Pushed Authorization Request with id '%s' on client with id '%s' failed to validate the PKCE policy: %s", requester.GetID(), client.GetID(), oidc.ErrorToDebugRFC6749Error(err))

ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, err)
ctx.Providers.OpenIDConnect.WritePushedAuthorizeError(ctx, rw, requester, err)

return
}

if err = client.ValidateResponseModePolicy(requester); err != nil {
ctx.Logger.Errorf("Pushed Authorization Request with id '%s' on client with id '%s' failed to validate the Response Mode: %s", requester.GetID(), client.GetID(), oidc.ErrorToDebugRFC6749Error(err))

ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, err)
ctx.Providers.OpenIDConnect.WritePushedAuthorizeError(ctx, rw, requester, err)

return
}
Expand Down
4 changes: 2 additions & 2 deletions internal/middlewares/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
"github.com/valyala/fasthttp"
)

// SetContentTypeApplicationJSON sets the Content-Type header to `application/json; charset=utf8`.
// SetContentTypeApplicationJSON sets the Content-Type header to `application/json; charset=utf-8`.
func SetContentTypeApplicationJSON(ctx *fasthttp.RequestCtx) {
ctx.SetContentTypeBytes(contentTypeApplicationJSON)
}

// SetContentTypeTextPlain sets the Content-Type header to `text/plain; charset=utf8`.
// SetContentTypeTextPlain sets the Content-Type header to `text/plain; charset=utf-8`.
func SetContentTypeTextPlain(ctx *fasthttp.RequestCtx) {
ctx.SetContentTypeBytes(contentTypeTextPlain)
}
6 changes: 4 additions & 2 deletions internal/oidc/client_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,6 @@ func clientCredentialsFromBasicAuth(header http.Header) (clientID, clientSecret
return clientID, clientSecret, true, nil
}

var _ fosite.TokenEndpointHandler = (*PKCEHandler)(nil)

// PKCEHandler is a fork of pkce.Handler with modifications to rectify bugs. It implements the
// fosite.TokenEndpointHandler.
type PKCEHandler struct {
Expand Down Expand Up @@ -743,3 +741,7 @@ func (c *PKCEHandler) CanSkipClientAuth(ctx context.Context, requester fosite.Ac
func (c *PKCEHandler) CanHandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) bool {
return requester.GetGrantTypes().ExactOne(GrantTypeAuthorizationCode)
}

var (
_ fosite.TokenEndpointHandler = (*PKCEHandler)(nil)
)
12 changes: 10 additions & 2 deletions internal/oidc/client_credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"testing"
"time"

"github.com/golang-jwt/jwt/v5"
jwt "github.com/golang-jwt/jwt/v5"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/ory/fosite"
Expand All @@ -23,7 +23,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/valyala/fasthttp"
"gopkg.in/square/go-jose.v2"
jose "gopkg.in/square/go-jose.v2"

"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration/schema"
Expand All @@ -33,6 +33,14 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)

func Test(t *testing.T) {
types := fosite.ResponseModeTypes{fosite.ResponseModeDefault, fosite.ResponseModeQuery, fosite.ResponseModeFragment, fosite.ResponseModeQuery}

assert.True(t, types.Has(fosite.ResponseModeDefault))
assert.True(t, types.Has(fosite.ResponseModeQuery))
assert.True(t, types.Has(fosite.ResponseModeFragment))
}

func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) {
hasher, err := oidc.NewHasher()

Expand Down
6 changes: 3 additions & 3 deletions internal/oidc/client_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ const (
func (c ClientConsentMode) String() string {
switch c {
case ClientConsentModeExplicit:
return explicit
return valueExplicit
case ClientConsentModeImplicit:
return implicit
return valueImplicit
case ClientConsentModePreConfigured:
return preconfigured
return valuePreconfigured
default:
return ""
}
Expand Down
6 changes: 5 additions & 1 deletion internal/oidc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func NewConfig(config *schema.OpenIDConnect, templates *templates.Provider) (c *
Templates: templates,
}

c.Handlers.ResponseMode = &ResponseModeHandler{c}

c.Strategy.Core = &HMACCoreStrategy{
Enigma: &hmac.HMACStrategy{Config: c},
Config: c,
Expand Down Expand Up @@ -253,7 +255,9 @@ func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) {
},
}

x := HandlersConfig{}
x := HandlersConfig{
ResponseMode: c.Handlers.ResponseMode,
}

for _, handler := range handlers {
if h, ok := handler.(fosite.AuthorizeEndpointHandler); ok {
Expand Down
41 changes: 24 additions & 17 deletions internal/oidc/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const (
ClaimRequestedAt = "rat"
ClaimExpirationTime = "exp"
ClaimAuthenticationTime = "auth_time"
ClaimIssuer = "iss"
ClaimIssuer = valueIss
ClaimSubject = "sub"
ClaimNonce = "nonce"
ClaimAudience = "aud"
Expand All @@ -37,8 +37,8 @@ const (
ClaimAuthorizedParty = "azp"
ClaimAuthenticationContextClassReference = "acr"
ClaimAuthenticationMethodsReference = "amr"
ClaimClientIdentifier = clientid
ClaimScope = scope
ClaimClientIdentifier = valueClientID
ClaimScope = valueScope
ClaimActive = "active"
ClaimUsername = "username"
ClaimTokenIntrospection = "token_introspection"
Expand Down Expand Up @@ -77,8 +77,8 @@ const (

// Grant Type strings.
const (
GrantTypeImplicit = implicit
GrantTypeRefreshToken = refreshtoken
GrantTypeImplicit = valueImplicit
GrantTypeRefreshToken = valueRefreshToken
GrantTypeAuthorizationCode = "authorization_code"
GrantTypeClientCredentials = "client_credentials"
)
Expand Down Expand Up @@ -106,7 +106,7 @@ const (
// JWS Algorithm strings.
// See: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
const (
SigningAlgNone = none
SigningAlgNone = valueNone

SigningAlgRSAUsingSHA256 = "RS256"
SigningAlgRSAUsingSHA384 = "RS384"
Expand Down Expand Up @@ -152,7 +152,7 @@ const (
const (
FormParameterState = "state"
FormParameterAuthorizationCode = "code"
FormParameterClientID = clientid
FormParameterClientID = valueClientID
FormParameterClientSecret = "client_secret"
FormParameterRequestURI = "request_uri"
FormParameterRedirectURI = "redirect_uri"
Expand All @@ -163,14 +163,15 @@ const (
FormParameterCodeChallengeMethod = "code_challenge_method"
FormParameterClientAssertionType = "client_assertion_type"
FormParameterClientAssertion = "client_assertion"
FormParameterScope = scope
FormParameterRefreshToken = refreshtoken
FormParameterScope = valueScope
FormParameterRefreshToken = valueRefreshToken
FormParameterIssuer = valueIss
FormParameterToken = "token"
FormParameterTokenTypeHint = "token_type_hint"
)

const (
PromptNone = none
PromptNone = valueNone
PromptLogin = "login"
PromptConsent = "consent"
// PromptCreate = "create" // This prompt value is currently unused.
Expand Down Expand Up @@ -202,6 +203,11 @@ const (
JWTHeaderTypeValueTokenIntrospectionJWT = "token-introspection+jwt"
)

const (
headerContentTypeTextHTML = "text/html; charset=utf-8"
headerContentTypeApplicationJSON = "application/json; charset=utf-8"
)

const (
tokenPrefixOrgAutheliaFmt = "authelia_%s_" //nolint:gosec
tokenPrefixOrgOryFmt = "ory_%s_" //nolint:gosec
Expand Down Expand Up @@ -324,13 +330,14 @@ const (
)

const (
scope = "scope"
clientid = "client_id"
implicit = "implicit"
explicit = "explicit"
preconfigured = "pre-configured"
none = "none"
refreshtoken = "refresh_token"
valueScope = "scope"
valueClientID = "client_id"
valueImplicit = "implicit"
valueExplicit = "explicit"
valuePreconfigured = "pre-configured"
valueNone = "none"
valueRefreshToken = "refresh_token"
valueIss = "iss"
)

const (
Expand Down
3 changes: 3 additions & 0 deletions internal/oidc/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnect) (config Ope
OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{
RequirePushedAuthorizationRequests: c.PAR.Enforce,
},
OAuth2IssuerIdentificationDiscoveryOptions: &OAuth2IssuerIdentificationDiscoveryOptions{
AuthorizationResponseIssuerParameterSupported: true,
},
},

OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
Expand Down
6 changes: 4 additions & 2 deletions internal/oidc/flow_client_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"github.com/ory/x/errorsx"
)

var _ fosite.TokenEndpointHandler = (*ClientCredentialsGrantHandler)(nil)

// ClientCredentialsGrantHandler handles access requests for the Client Credentials Flow.
type ClientCredentialsGrantHandler struct {
*oauth2.HandleHelper
Expand Down Expand Up @@ -90,3 +88,7 @@ func (c *ClientCredentialsGrantHandler) CanHandleTokenEndpointRequest(ctx contex
// Value MUST be set to "client_credentials".
return requester.GetGrantTypes().ExactOne(GrantTypeClientCredentials)
}

var (
_ fosite.TokenEndpointHandler = (*ClientCredentialsGrantHandler)(nil)
)
6 changes: 4 additions & 2 deletions internal/oidc/flow_refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (
"github.com/pkg/errors"
)

var _ fosite.TokenEndpointHandler = (*RefreshTokenGrantHandler)(nil)

// RefreshTokenGrantHandler handles access requests for the Refresh Token Flow.
type RefreshTokenGrantHandler struct {
AccessTokenStrategy oauth2.AccessTokenStrategy
Expand Down Expand Up @@ -318,3 +316,7 @@ func RefreshFlowSanitizeRestoreOriginalRequestBasic(r, o fosite.Requester) fosit

return sr
}

var (
_ fosite.TokenEndpointHandler = (*RefreshTokenGrantHandler)(nil)
)

0 comments on commit 0da4abf

Please sign in to comment.