feat(oidc): client_secret_jwt client auth (#5031)

This theoretically adds support for client_secret_jwt.
pull/4546/head
James Elliott 2023-03-06 13:35:58 +11:00 committed by GitHub
parent 0565be3db1
commit 42671d3edb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 210 additions and 82 deletions

View File

@ -6,7 +6,7 @@ info:
Authelia is an open-source authentication and authorization server providing 2-factor authentication and single
sign-on (SSO) for your applications via a web portal.
contact:
name: Authelia Support
name: Support
url: https://www.authelia.com/contact/
email: team@authelia.com
license:
@ -2940,9 +2940,9 @@ components:
- "address"
- "phone"
openid.spec.IntrospectionRequest:
type: object
required:
- "token"
type: object
properties:
token:
description: >
@ -2952,8 +2952,8 @@ components:
this is the "refresh_token" value returned from the token endpoint
as defined in OAuth 2.0 [RFC6749], Section 5.1. Other token types
are outside the scope of this specification.
type: string
example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn"
type: string
token_type_hint:
description: >
A hint about the type of the token submitted for
@ -2965,27 +2965,61 @@ components:
is able to detect the token type automatically. Values for this
field are defined in the "OAuth Token Type Hints" registry defined
in OAuth Token Revocation [RFC7009].
type: string
example: "access_token"
enum:
- "access_token"
- "refresh_token"
example: "access_token"
type: string
openid.spec.AccessRequest.ClientAuth:
oneOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Base'
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Secret'
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.JWT'
openid.spec.AccessRequest.ClientAuth.Base:
required:
- "client_id"
type: object
properties:
client_id:
description: >
REQUIRED if the client is not authenticating with the
authorization server as described in Section 3.2.1. of [RFC6749].
The client identifier as described in Section 2.2 of [RFC6749].
REQUIRED if the client is not authenticating with the authorization server as described in
Section 3.2.1. of [RFC6749]. The client identifier as described in Section 2.2 of [RFC6749].
example: "my_client"
type: string
example: "authelia_dc_mn123kjn12kj3123njk"
openid.spec.AccessRequest.ClientAuth.Secret:
required:
- "client_secret"
type: object
properties:
client_secret:
description: >
REQUIRED. The client secret. The client MAY omit the
parameter if the client secret is an empty string.
type: string
format: password
type: string
openid.spec.AccessRequest.ClientAuth.JWT:
allOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Base'
- type: object
required:
- "client_assertion"
- "client_assertion_type"
properties:
client_assertion:
description: >
The value of the client_assertion_type parameter MUST be
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
enum:
- "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
example: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
type: string
client_assertion_type:
description: >
A JWT signed with HS256 using the client secret value or RS256 using a registered public key.
Theoretically a properly formed JWT signed using HS256 with the client secret as the HMAC key should
work but this has not been tested.
format: password
type: string
openid.spec.AccessRequest.AuthorizationCodeFlow:
allOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth'
@ -2995,22 +3029,22 @@ components:
- "grant_type"
properties:
grant_type:
description: Value MUST be set to "urn:ietf:params:oauth:grant-type:device_code".
type: string
description: Value MUST be set to "code".
enum:
- "authorization_code"
type: string
code:
description: The Authorization Code.
type: string
example: "authelia_ac_1j2kn3knj12n3kj12n"
type: string
code_verifier:
description: The Authorization Code Verifier (PKCE).
type: string
example: "88a25754f7c0b3b3b88cf6cd4e29e8356b160524fdc1cb329a94471825628fd3"
type: string
redirect_uri:
description: The original Redirect URI used in the Authorization Request.
type: string
example: "https://app.example.com/oidc/callback"
type: string
openid.spec.AccessRequest.DeviceCodeFlow:
allOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth'
@ -3021,13 +3055,13 @@ components:
properties:
grant_type:
description: Value MUST be set to "urn:ietf:params:oauth:grant-type:device_code".
type: string
enum:
- "urn:ietf:params:oauth:grant-type:device_code"
type: string
device_code:
description: The Device Authorization Code.
type: string
example: "authelia_dc_mn123kjn12kj3123njk"
type: string
openid.spec.AccessRequest.RefreshTokenFlow:
allOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth'
@ -3038,12 +3072,13 @@ components:
properties:
grant_type:
description: Value MUST be set to "refresh_token".
type: string
enum:
- "refresh_token"
type: string
refresh_token:
description: The Refresh Token.
example: "authelia_rt_1n2j3kihn12kj3n12k"
type: string
scope:
description: >
The scope of the access request as described by
@ -3051,20 +3086,30 @@ components:
not originally granted by the resource owner, and if omitted is
treated as equal to the scope originally granted by the
resource owner.
example: "openid profile groups"
type: string
openid.spec.AccessResponse:
type: object
required:
- "access_token"
- "token_type"
- "expires_in"
properties:
access_token:
description: The access token issued by the authorization server.
type: string
example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn"
refresh_token:
type: string
id_token:
description: The id token issued by the authorization server.
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
type: string
refresh_token:
description: >
The refresh token, which can be used to obtain new access tokens using the
same authorization grant as described in Section 6.
token_type:
example: "authelia_rt_kGBoSMbfVGP2RR6Kvujv3Xg7uXV2i"
type: string
token_type:
description: >
The access token type provides the client with the information
required to successfully utilize the access token to make a protected
@ -3073,21 +3118,26 @@ components:
type.
enum:
- "bearer"
example: "bearer"
type: string
expires_in:
type: integer
description: >
The lifetime in seconds of the access token. For
example, the value "3600" denotes that the access token will
expire in one hour from the time the response was generated.
If omitted, the authorization server SHOULD provide the
expiration time via other means or document the default value.
example: 3600
type: integer
state:
type: string
description: Exactly the state value passed in the authorization request if present.
scope:
example: "5dVZhNfri5XZS6wadskuzUk4MHYCvEcUgidjMeBjsktAhY7EKB"
type: string
scope:
description: >
The scope of the access token as described by Section 3.3 if it differs from the requested scope.
example: "openid profile groups"
type: string
openid.spec.AuthorizeRequest:
type: object
required:
@ -3098,14 +3148,14 @@ components:
properties:
scope:
description: The requested scope.
type: string
example: "openid profile groups"
type: string
response_type:
$ref: '#/components/schemas/openid.spec.ResponseType'
client_id:
description: The OAuth 2.0 client identifier.
type: string
example: "app"
type: string
redirect_uri:
description: >
Redirection URI to which the response will be sent. This URI MUST exactly match one of the
@ -3115,15 +3165,15 @@ components:
that the Client Type is confidential, as defined in Section 2.1 of OAuth 2.0, and provided the OP
allows the use of http Redirection URIs in this case. The Redirection URI MAY use an alternate
scheme, such as one that is intended to identify a callback into a native application.
type: string
example: "https://app.example.com"
type: string
state:
description: >
Opaque value used to maintain state between the request and the callback. Typically, Cross-Site
Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this
parameter with a browser cookie.
type: string
example: "oV84Vsy7wyCgRk2h4aZBmXZq4q3g2f"
type: string
response_mode:
$ref: '#/components/schemas/openid.spec.ResponseMode'
nonce:
@ -3132,14 +3182,23 @@ components:
The value is passed through unmodified from the Authentication Request to the ID Token. Sufficient
entropy MUST be present in the nonce values used to prevent attackers from guessing values. For
implementation notes, see Section 15.5.2.
type: string
example: "TRMLqchoKGQNcooXvBvUy9PtmLdJGf"
type: string
display:
$ref: '#/components/schemas/openid.spec.DisplayType'
prompt:
description: >
Not Supported: Space delimited, case sensitive list of ASCII string values that specifies whether
the Authorization Server prompts the End-User for reauthentication and consent.
enum:
- "none"
- "login"
- "consent"
- "select_account"
- "login consent"
- "login select_account"
- "consent select_account"
example: "consent"
type: string
max_age:
description: >
@ -3217,34 +3276,32 @@ components:
description: >
A Subject Identifier is a locally unique and never reassigned identifier within the Issuer for the
End-User, which is intended to be consumed by the Client.
type: string
enum:
- "public"
- "pairwise"
type: string
openid.spec.ClientAuthMethod:
description: The OAuth 2.0 / OpenID Connect 1.0 Client Authentication Method.
type: string
enum:
- "client_secret_basic"
- "client_secret_post"
- "client_secret_jwt"
- "private_key_jwt"
- "none"
type: string
openid.spec.DisplayType:
description: >
ASCII string value that specifies how the Authorization Server displays the authentication and consent user
interface pages to the End-User.
type: string
example: "page"
enum:
- "page"
- "popup"
- "touch"
- "wap"
example: "page"
type: string
openid.spec.ResponseType:
description: The OAuth 2.0 / OpenID Connect 1.0 Response Type.
type: string
example: "code"
enum:
- "code"
- "id_token"
@ -3254,21 +3311,21 @@ components:
- "token id_token"
- "code id_token token"
- "none"
example: "code"
type: string
openid.spec.ResponseMode:
description: >
Informs the Authorization Server of the mechanism to be used for returning parameters from the Authorization
Endpoint. This use of this parameter is NOT RECOMMENDED when the Response Mode that would be requested is
the default mode specified for the Response Type.
type: string
example: "query"
enum:
- "query"
- "fragment"
- "form_post"
example: "query"
type: string
openid.spec.GrantType:
description: The OAuth 2.0 / OpenID Connect 1.0 Grant Type.
type: string
example: "authorization_code"
enum:
- "authorization_code"
- "refresh_token"
@ -3276,21 +3333,23 @@ components:
- "password"
- "client_credentials"
- "urn:ietf:params:oauth:grant-type:device_code"
example: "authorization_code"
type: string
openid.spec.CodeChallengeMethod:
description: The RFC7636 Code Challenge Verifier Method.
type: string
example: "S256"
enum:
- "plain"
- "S256"
example: "S256"
type: string
openid.spec.ClaimType:
description: The representation of claims.
type: string
example: "normal"
enum:
- "normal"
- "aggregated"
- "distributed"
example: "normal"
type: string
jose.spec.None:
description: The JSON Web Signature Algorithm
type: string
@ -3298,13 +3357,12 @@ components:
- "none"
jose.spec.JWS.None:
description: The JSON Web Signature Algorithm
type: string
oneOf:
- $ref: '#/components/schemas/jose.spec.None'
- $ref: '#/components/schemas/jose.spec.jws'
type: string
jose.spec.jws:
description: The JSON Web Signature Algorithm
type: string
enum:
- "HS256"
- "HS384"
@ -3318,9 +3376,9 @@ components:
- "PS256"
- "PS384"
- "PS512"
type: string
jose.spec.JWE.alg:
description: The JSON Web Encryption Algorithm (CEK)
type: string
enum:
- "RSA1_5"
- "RSA-OAEP"
@ -3339,9 +3397,9 @@ components:
- "PBES2-HS256+A128KW"
- "PBES2-HS384+A192KW"
- "PBES2-HS512+A256KW"
type: string
jose.spec.JWE.enc:
description: The JSON Web Encryption Algorithm (Claims)
type: string
enum:
- "A128CBC-HS256"
- "A192CBC-HS384"
@ -3350,6 +3408,7 @@ components:
- "A256CBC"
- "A128GCM"
- "A256GCM"
type: string
jose.spec.JWK.base:
type: object
properties:
@ -3359,21 +3418,20 @@ components:
the public key. The "use" parameter is employed to indicate whether
a public key is used for encrypting data or verifying the signature
on data.
type: string
example: "sig"
enum:
- "sig"
- "enc"
example: "sig"
type: string
key_ops:
description: >
The "key_ops" (key operations) parameter identifies the operation(s)
for which the key is intended to be used. The "key_ops" parameter is
intended for use cases in which public, private, or symmetric keys
may be present.
type: array
example: ["sign"]
type: array
items:
type: string
enum:
- "sign"
- "verify"
@ -3383,6 +3441,7 @@ components:
- "unwrapKey"
- "deriveKey"
- "deriveBits"
type: string
kid:
description: >
The "kid" (key ID) parameter is used to match a specific key. This
@ -3427,8 +3486,8 @@ components:
OPTIONAL.
type: array
items:
type: string
format: byte
type: string
x5t:
description: >
The "x5t" (X.509 certificate SHA-1 thumbprint) parameter is a
@ -3437,8 +3496,8 @@ components:
thumbprints are also sometimes known as certificate fingerprints.
The key in the certificate MUST match the public key represented by
other members of the JWK. Use of this member is OPTIONAL.
type: string
format: byte
type: string
x5t#S256:
description: >
The "x5t#S256" (X.509 certificate SHA-256 thumbprint) parameter is a
@ -3447,17 +3506,17 @@ components:
thumbprints are also sometimes known as certificate fingerprints.
The key in the certificate MUST match the public key represented by
other members of the JWK. Use of this member is OPTIONAL.
type: string
format: byte
type: string
jose.spec.JWK.RSA:
description: RSA Public Key in JSON Web Key format as defined by RFC7517 and RFC7518.
allOf:
- $ref: '#/components/schemas/jose.spec.JWK.base'
- type: object
required:
- required:
- "kty"
- "n"
- "e"
type: object
properties:
kty:
description: >

View File

@ -6,6 +6,7 @@ import (
"hash"
"html/template"
"net/url"
"path"
"time"
"github.com/hashicorp/go-retryablehttp"
@ -515,6 +516,17 @@ func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) (tmpl *template.Te
// GetTokenURL returns the token URL.
func (c *Config) GetTokenURL(ctx context.Context) (tokenURL string) {
if ctx, ok := ctx.(OpenIDConnectContext); ok {
tokenURI, err := ctx.IssuerURL()
if err != nil {
return c.TokenURL
}
tokenURI.Path = path.Join(tokenURI.Path, EndpointPathToken)
return tokenURI.String()
}
return c.TokenURL
}

View File

@ -73,6 +73,25 @@ const (
GrantTypeClientCredentials = "client_credentials"
)
// Client Auth Method strings.
const (
ClientAuthMethodClientSecretBasic = "client_secret_basic"
ClientAuthMethodClientSecretPost = "client_secret_post"
ClientAuthMethodClientSecretJWT = "client_secret_jwt"
ClientAuthMethodNone = "none"
)
// Response Type strings.
const (
ResponseTypeAuthorizationCodeFlow = "code"
ResponseTypeImplicitFlowIDToken = "id_token"
ResponseTypeImplicitFlowToken = "token"
ResponseTypeImplicitFlowBoth = "id_token token"
ResponseTypeHybridFlowIDToken = "code id_token"
ResponseTypeHybridFlowToken = "code token"
ResponseTypeHybridFlowBoth = "code id_token token"
)
// Signing Algorithm strings.
const (
SigningAlgorithmNone = none

View File

@ -8,14 +8,18 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clien
SubjectTypePublic,
},
ResponseTypesSupported: []string{
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none",
ResponseTypeAuthorizationCodeFlow,
ResponseTypeImplicitFlowIDToken,
ResponseTypeImplicitFlowToken,
ResponseTypeImplicitFlowBoth,
ResponseTypeHybridFlowIDToken,
ResponseTypeHybridFlowToken,
ResponseTypeHybridFlowBoth,
},
GrantTypesSupported: []string{
GrantTypeAuthorizationCode,
GrantTypeImplicit,
GrantTypeRefreshToken,
},
ResponseModesSupported: []string{
ResponseModeFormPost,
@ -49,6 +53,12 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clien
ClaimPreferredUsername,
ClaimFullName,
},
TokenEndpointAuthMethodsSupported: []string{
ClientAuthMethodClientSecretBasic,
ClientAuthMethodClientSecretPost,
ClientAuthMethodClientSecretJWT,
ClientAuthMethodNone,
},
},
OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
CodeChallengeMethodsSupported: []string{

View File

@ -142,15 +142,25 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Len(t, disco.SubjectTypesSupported, 1)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
assert.Len(t, disco.ResponseTypesSupported, 8)
assert.Contains(t, disco.ResponseTypesSupported, "code")
assert.Contains(t, disco.ResponseTypesSupported, "token")
assert.Contains(t, disco.ResponseTypesSupported, "id_token")
assert.Contains(t, disco.ResponseTypesSupported, "code token")
assert.Contains(t, disco.ResponseTypesSupported, "code id_token")
assert.Contains(t, disco.ResponseTypesSupported, "token id_token")
assert.Contains(t, disco.ResponseTypesSupported, "code token id_token")
assert.Contains(t, disco.ResponseTypesSupported, "none")
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
@ -231,15 +241,25 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.Len(t, disco.SubjectTypesSupported, 1)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
assert.Len(t, disco.ResponseTypesSupported, 8)
assert.Contains(t, disco.ResponseTypesSupported, "code")
assert.Contains(t, disco.ResponseTypesSupported, "token")
assert.Contains(t, disco.ResponseTypesSupported, "id_token")
assert.Contains(t, disco.ResponseTypesSupported, "code token")
assert.Contains(t, disco.ResponseTypesSupported, "code id_token")
assert.Contains(t, disco.ResponseTypesSupported, "token id_token")
assert.Contains(t, disco.ResponseTypesSupported, "code token id_token")
assert.Contains(t, disco.ResponseTypesSupported, "none")
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
assert.Len(t, disco.ClaimsSupported, 18)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)

View File

@ -1,6 +1,7 @@
package oidc
import (
"context"
"net/url"
"time"
@ -643,3 +644,10 @@ type OpenIDConnectWellKnownConfiguration struct {
OpenIDConnectFrontChannelLogoutDiscoveryOptions
OpenIDConnectBackChannelLogoutDiscoveryOptions
}
// OpenIDConnectContext represents the context implementation that is used by some OpenID Connect 1.0 implementations.
type OpenIDConnectContext interface {
context.Context
IssuerURL() (issuerURL *url.URL, err error)
}