Skip to content

Commit

Permalink
Make it possible to configure server endpoints manually (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbilski authored Jul 16, 2024
1 parent f6c72a2 commit bbe0b8a
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 65 deletions.
103 changes: 64 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,45 +83,54 @@ oauth2c [issuer url] [flags]
The available flags are:

```sh
--acr-values strings ACR values
--actor-token string acting party token
--actor-token-type string acting party token type
--assertion string claims for jwt bearer assertion
--audience strings requested audience
--auth-method string token endpoint authentication method
--browser-timeout duration browser timeout (default 10m0s)
--claims string use claims
--client-id string client identifier
--client-secret string client secret
--dpop use DPoP
--encrypted-request-object pass request parameters as encrypted jwt
--encryption-key string path or url to encryption key in jwks format
--grant-type string grant type
-h, --help help for oauthc
--http-timeout duration http client timeout (default 1m0s)
--id-token-hint string id token hint
--idp-hint string identity provider hint
--insecure allow insecure connections
--login-hint string user identifier hint
--no-prompt disable prompt
--par enable pushed authorization requests (PAR)
--password string resource owner password credentials grant flow password
--pkce enable proof key for code exchange (PKCE)
--rar string use rich authorization request (RAR)
--redirect-url string client redirect url (default "http://localhost:9876/callback")
--refresh-token string refresh token
--request-object pass request parameters as jwt
--response-mode string response mode
--response-types strings response type
--scopes strings requested scopes
--signing-key string path or url to signing key in jwks format
-s, --silent silent mode
--subject-token string third party token
--subject-token-type string third party token type
--tls-cert string path to tls cert pem file
--tls-key string path to tls key pem file
--tls-root-ca string path to tls root ca pem file
--username string resource owner password credentials grant flow username
--acr-values strings ACR values
--actor-token string acting party token
--actor-token-type string acting party token type
--assertion string claims for jwt bearer assertion
--audience strings requested audience
--auth-method string token endpoint authentication method
--authorization-endpoint string server's authorization endpoint
--browser-timeout duration browser timeout (default 10m0s)
--callback-tls-cert string path to callback tls cert pem file
--callback-tls-key string path to callback tls key pem file
--claims string use claims
--client-id string client identifier
--client-secret string client secret
--device-authorization-endpoint string server's device authorization endpoint
--dpop use DPoP
--encrypted-request-object pass request parameters as encrypted jwt
--encryption-key string path or url to encryption key in jwks format
--grant-type string grant type
-h, --help help for oauth2c
--http-timeout duration http client timeout (default 1m0s)
--id-token-hint string id token hint
--idp-hint string identity provider hint
--insecure allow insecure connections
--login-hint string user identifier hint
--mtls-pushed-authorization-request-endpoint string server's mtls pushed authorization request endpoint
--mtls-token-endpoint string server's mtls token endpoint
--no-prompt disable prompt
--par enable pushed authorization requests (PAR)
--password string resource owner password credentials grant flow password
--pkce enable proof key for code exchange (PKCE)
--purpose string string describing the purpose for obtaining End-User authorization
--pushed-authorization-request-endpoint string server's pushed authorization request endpoint
--rar string use rich authorization request (RAR)
--redirect-url string client redirect url (default "http://localhost:9876/callback")
--refresh-token string refresh token
--request-object pass request parameters as jwt
--response-mode string response mode
--response-types strings response type
--scopes strings requested scopes
--signing-key string path or url to signing key in jwks format
-s, --silent silent mode
--subject-token string third party token
--subject-token-type string third party token type
--tls-cert string path to tls cert pem file
--tls-key string path to tls key pem file
--tls-root-ca string path to tls root ca pem file
--token-endpoint string server's token endpoint
--username string resource owner password credentials grant flow username
```

`oauth2c` opens a browser for flows such as authorization code and starts an
Expand Down Expand Up @@ -710,6 +719,22 @@ oauth2c https://oauth2c.us.authz.cloudentity.io/oauth2c/demo \
--callback-tls-key https://raw.githubusercontent.com/cloudentity/oauth2c/master/data/key.pem
```

#### Specifying Authorization Server's Endpoint Manually

If your authorization server does not support OIDC, you can specify the endpoint manually using flags.

```sh
oauth2c https://oauth2c.us.authz.cloudentity.io/oauth2c/demo \
--client-id cauktionbud6q8ftlqq0 \
--client-secret HCwQ5uuUWBRHd04ivjX5Kl0Rz8zxMOekeLtqzki0GPc \
--response-types code \
--response-mode query \
--grant-type authorization_code \
--auth-method client_secret_basic \
--token-endpoint https://oauth2c.us.authz.cloudentity.io/oauth2c/demo/oauth2/token \
--authorization-endpoint https://oauth2c.us.authz.cloudentity.io/oauth2c/demo/oauth2/authorize
```

## License

`oauth2c` is released under the
Expand Down
41 changes: 28 additions & 13 deletions cmd/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ type OAuth2Cmd struct {
}

func NewOAuth2Cmd(version, commit, date string) (cmd *OAuth2Cmd) {
var cconfig oauth2.ClientConfig
var (
cconfig oauth2.ClientConfig
sconfig oauth2.ServerConfig
)

cmd = &OAuth2Cmd{
Command: &cobra.Command{
Expand All @@ -38,7 +41,7 @@ func NewOAuth2Cmd(version, commit, date string) (cmd *OAuth2Cmd) {
},
}

cmd.Command.Run = cmd.Run(&cconfig)
cmd.Command.Run = cmd.Run(&cconfig, &sconfig)

cmd.AddCommand(NewVersionCmd(version, commit, date))
cmd.AddCommand(docsCmd)
Expand Down Expand Up @@ -85,10 +88,17 @@ func NewOAuth2Cmd(version, commit, date string) (cmd *OAuth2Cmd) {
cmd.PersistentFlags().StringSliceVar(&cconfig.ACRValues, "acr-values", []string{}, "ACR values")
cmd.PersistentFlags().StringVar(&cconfig.Purpose, "purpose", "", "string describing the purpose for obtaining End-User authorization")

cmd.PersistentFlags().StringVar(&sconfig.TokenEndpoint, "token-endpoint", "", "server's token endpoint")
cmd.PersistentFlags().StringVar(&sconfig.AuthorizationEndpoint, "authorization-endpoint", "", "server's authorization endpoint")
cmd.PersistentFlags().StringVar(&sconfig.DeviceAuthorizationEndpoint, "device-authorization-endpoint", "", "server's device authorization endpoint")
cmd.PersistentFlags().StringVar(&sconfig.PushedAuthorizationRequestEndpoint, "pushed-authorization-request-endpoint", "", "server's pushed authorization request endpoint")
cmd.PersistentFlags().StringVar(&sconfig.MTLsEndpointAliases.TokenEndpoint, "mtls-token-endpoint", "", "server's mtls token endpoint")
cmd.PersistentFlags().StringVar(&sconfig.MTLsEndpointAliases.PushedAuthorizationRequestEndpoint, "mtls-pushed-authorization-request-endpoint", "", "server's mtls pushed authorization request endpoint")

return cmd
}

func (c *OAuth2Cmd) Run(cconfig *oauth2.ClientConfig) func(cmd *cobra.Command, args []string) {
func (c *OAuth2Cmd) Run(cconfig *oauth2.ClientConfig, sconfig *oauth2.ServerConfig) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
var (
config Config
Expand Down Expand Up @@ -148,7 +158,7 @@ func (c *OAuth2Cmd) Run(cconfig *oauth2.ClientConfig) func(cmd *cobra.Command, a
}
}

if err := c.Authorize(*cconfig, hc); err != nil {
if err := c.Authorize(*cconfig, *sconfig, hc); err != nil {
var oauth2Error *oauth2.Error

if errors.As(err, &oauth2Error) {
Expand All @@ -164,21 +174,26 @@ func (c *OAuth2Cmd) Run(cconfig *oauth2.ClientConfig) func(cmd *cobra.Command, a
}
}

func (c *OAuth2Cmd) Authorize(clientConfig oauth2.ClientConfig, hc *http.Client) error {
func (c *OAuth2Cmd) Authorize(
clientConfig oauth2.ClientConfig,
serverConfig oauth2.ServerConfig,
hc *http.Client,
) error {
var (
serverRequest oauth2.Request
serverConfig oauth2.ServerConfig
err error
)

// openid configuration
if serverRequest, serverConfig, err = oauth2.FetchOpenIDConfiguration(
context.Background(),
clientConfig.IssuerURL,
hc,
); err != nil {
LogRequestln(serverRequest)
return err
if !serverConfig.IsConfigured() {
if serverRequest, serverConfig, err = oauth2.FetchOpenIDConfiguration(
context.Background(),
clientConfig.IssuerURL,
hc,
); err != nil {
LogRequestln(serverRequest)
return err
}
}

if !silent && !noPrompt {
Expand Down
2 changes: 1 addition & 1 deletion internal/oauth2/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func RequestObjectClaims(params url.Values, serverConfig ServerConfig, clientCon
return func() (map[string]interface{}, error) {
claims := map[string]interface{}{
"iss": clientConfig.ClientID,
"aud": serverConfig.Issuer,
"aud": clientConfig.IssuerURL,
"exp": time.Now().Add(time.Minute * 10).Unix(),
"nbf": time.Now().Unix(),
}
Expand Down
2 changes: 1 addition & 1 deletion internal/oauth2/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ func TestSignJWT(t *testing.T) {

claims := AssertionClaims(
ServerConfig{
Issuer: "https://example.com/tid/aid",
TokenEndpoint: "https://example.com/tid/aid/oauth2/token",
},
ClientConfig{
IssuerURL: "https://example.com/tid/aid",
Assertion: `{"sub": "[email protected]"}`,
},
)
Expand Down
16 changes: 16 additions & 0 deletions internal/oauth2/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ type ClientConfig struct {
}

func RequestAuthorization(cconfig ClientConfig, sconfig ServerConfig, hc *http.Client) (r Request, codeVerifier string, err error) {
if sconfig.AuthorizationEndpoint == "" {
return r, "", errors.New("the server's authorization endpoint is not configured")
}

if r.URL, err = url.Parse(sconfig.AuthorizationEndpoint); err != nil {
return r, "", errors.Wrapf(err, "failed to parse authorization endpoint")
}
Expand Down Expand Up @@ -127,6 +131,14 @@ func RequestPAR(
endpoint string
)

if sconfig.AuthorizationEndpoint == "" {
return parRequest, parResponse, authorizeRequest, "", errors.New("the server's authorization endpoint is not configured")
}

if sconfig.PushedAuthorizationRequestEndpoint == "" && sconfig.MTLsEndpointAliases.PushedAuthorizationRequestEndpoint == "" {
return parRequest, parResponse, authorizeRequest, "", errors.New("the server's pushed authorization request endpoint is not configured")
}

// push authorization request to /par
if codeVerifier, err = parRequest.AuthorizeRequest(cconfig, sconfig, hc); err != nil {
return parRequest, parResponse, authorizeRequest, "", errors.Wrapf(err, "failed to create authorization request")
Expand Down Expand Up @@ -382,6 +394,10 @@ func RequestToken(
body []byte
)

if sconfig.TokenEndpoint == "" && sconfig.MTLsEndpointAliases.TokenEndpoint == "" {
return request, response, errors.New("the server's token endpoint is not configured")
}

for _, opt := range opts {
opt(&params)
}
Expand Down
6 changes: 6 additions & 0 deletions internal/oauth2/oauth2_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"net/http"
"net/url"
"strings"

"github.com/pkg/errors"
)

type DeviceAuthorizationResponse struct {
Expand All @@ -24,6 +26,10 @@ func RequestDeviceAuthorization(ctx context.Context, cconfig ClientConfig, sconf
resp *http.Response
)

if sconfig.DeviceAuthorizationEndpoint == "" {
return request, response, errors.New("the server's device authorization endpoint is not configured")
}

request.Form = url.Values{
"client_id": {cconfig.ClientID},
}
Expand Down
32 changes: 21 additions & 11 deletions internal/oauth2/well_known.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,31 @@ import (
const OpenIDConfigurationPath = "/.well-known/openid-configuration"

type ServerConfig struct {
Issuer string `json:"issuer"`
JWKsURI string `json:"jwks_uri"`
SupportedGrantTypes []string `json:"grant_types_supported"`
SupportedResponseTypes []string `json:"response_types_supported"`
SupportedTokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported"`
SupportedScopes []string `json:"scopes_supported"`
SupportedResponseModes []string `json:"response_modes_supported"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
SupportedGrantTypes []string `json:"grant_types_supported"`
SupportedResponseTypes []string `json:"response_types_supported"`
SupportedTokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported"`
SupportedScopes []string `json:"scopes_supported"`
SupportedResponseModes []string `json:"response_modes_supported"`

AuthorizationEndpoint string `json:"authorization_endpoint"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
MTLsEndpointAliases struct {
TokenEndpoint string `json:"token_endpoint"`
PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint"`
} `json:"mtls_endpoint_aliases"`

JWKsURI string `json:"jwks_uri"`
}

func (c ServerConfig) IsConfigured() bool {
return c.AuthorizationEndpoint != "" ||
c.DeviceAuthorizationEndpoint != "" ||
c.PushedAuthorizationRequestEndpoint != "" ||
c.TokenEndpoint != "" ||
c.MTLsEndpointAliases.TokenEndpoint != "" ||
c.MTLsEndpointAliases.PushedAuthorizationRequestEndpoint != ""
}

func FetchOpenIDConfiguration(ctx context.Context, issuerURL string, hc *http.Client) (request Request, c ServerConfig, err error) {
Expand Down

0 comments on commit bbe0b8a

Please sign in to comment.