oidc-demo-app/internal/services/oidc.go

129 lines
3.4 KiB
Go
Raw Permalink Normal View History

/*
Copyright CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package services
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"net/url"
"time"
"github.com/lestrrat-go/jwx/jwk"
"golang.org/x/oauth2"
2023-07-29 15:53:26 +00:00
"code.cacert.org/cacert/oidc-demo-app/internal/models"
)
// OidcParams defines the parameters for DiscoverOIDC
type OidcParams struct {
OidcServer string
OidcClientID string
OidcClientSecret string
APIClient *http.Client
}
type OIDCInformation struct {
KeySet jwk.Set
OAuth2Config *oauth2.Config
OIDCConfiguration *models.OpenIDConfiguration
}
// DiscoverOIDC gets OpenID Connect parameters from the discovery endpoint and the
// JSON Web Key Set from the discovered jwksUri.
//
// The subset of values specified by models.OpenIDConfiguration is stored in
// the given context and can be retrieved from the context by GetOidcConfig.
//
// OAuth2 specific values are stored in another context object and can be
// retrieved by GetOAuth2Config.
//
// The JSON Web Key Set can be retrieved by GetJwkSet.
func DiscoverOIDC(logger *slog.Logger, params *OidcParams) (*OIDCInformation, error) {
discoveryURL, err := url.Parse(params.OidcServer)
if err != nil {
logger.Error(
"could not parse parameter oidc.server as URL",
"oidc.server", params.OidcServer,
)
return nil, fmt.Errorf("could not parse parameter value: %w", err)
}
2024-05-12 10:12:27 +00:00
discoveryURL.Path = "/.well-known/openid-configuration"
var (
body []byte
req *http.Request
)
req, err = http.NewRequest(http.MethodGet, discoveryURL.String(), bytes.NewBuffer(body))
if err != nil {
return nil, fmt.Errorf("could not create OIDC discovery request: %w", err)
}
req.Header = map[string][]string{
"Accept": {"application/json"},
}
resp, err := params.APIClient.Do(req)
if err != nil {
return nil, fmt.Errorf("call to OIDC discovery endpoint failed: %w", err)
}
defer func() { _ = resp.Body.Close() }()
dec := json.NewDecoder(resp.Body)
discoveryResponse := &models.OpenIDConfiguration{}
err = dec.Decode(discoveryResponse)
if err != nil {
return nil, fmt.Errorf("could not decode OIDC discovery response: %w", err)
}
oauth2Config := &oauth2.Config{
ClientID: params.OidcClientID,
ClientSecret: params.OidcClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: discoveryResponse.AuthorizationEndpoint,
TokenURL: discoveryResponse.TokenEndpoint,
},
Scopes: []string{"openid", "email", "profile"},
}
const jwkFetchTimeout = 10 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), jwkFetchTimeout)
defer cancel()
keySet, err := jwk.Fetch(ctx, discoveryResponse.JwksURI, jwk.WithHTTPClient(params.APIClient))
if err != nil {
return nil, fmt.Errorf("could not fetch JWKs: %w", err)
}
return &OIDCInformation{
KeySet: keySet,
OAuth2Config: oauth2Config,
OIDCConfiguration: discoveryResponse,
}, nil
}