mirror of
https://github.com/davidallendj/opaal.git
synced 2025-12-20 03:27:02 -07:00
Made changes to get client credentials grant working
This commit is contained in:
parent
e67bc3e010
commit
5173701fa0
7 changed files with 135 additions and 64 deletions
|
|
@ -85,6 +85,7 @@ var loginCmd = &cobra.Command{
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// start the listener
|
||||
err := opaal.Login(&config, &client, provider)
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
|
|
|
|||
|
|
@ -8,33 +8,34 @@ import (
|
|||
type ClientCredentialsFlowParams struct {
|
||||
State string `yaml:"state"`
|
||||
ResponseType string `yaml:"response-type"`
|
||||
Client *oauth.Client
|
||||
}
|
||||
|
||||
type ClientCredentialsFlowEndpoints struct {
|
||||
Create string
|
||||
Clients string
|
||||
Authorize string
|
||||
Token string
|
||||
}
|
||||
|
||||
func NewClientCredentialsFlow(eps ClientCredentialsFlowEndpoints, client *oauth.Client) error {
|
||||
func NewClientCredentialsFlow(eps ClientCredentialsFlowEndpoints, params ClientCredentialsFlowParams) (string, error) {
|
||||
// register a new OAuth 2 client with authorization srever
|
||||
_, err := client.CreateOAuthClient(eps.Create)
|
||||
res, err := params.Client.CreateOAuthClient(eps.Clients, []oauth.GrantType{oauth.ClientCredentials})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register OAuth client: %v", err)
|
||||
return "", fmt.Errorf("failed to register OAuth client: %v", err)
|
||||
}
|
||||
|
||||
// authorize the client
|
||||
_, err = client.AuthorizeOAuthClient(eps.Authorize)
|
||||
res, err = params.Client.AuthorizeOAuthClient(eps.Authorize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authorize client: %v", err)
|
||||
return "", fmt.Errorf("failed to authorize client: %v", err)
|
||||
}
|
||||
|
||||
// request a token from the authorization server
|
||||
res, err := client.PerformTokenGrant(eps.Token, "")
|
||||
res, err = params.Client.PerformClientCredentialsTokenGrant(eps.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch token from authorization server: %v", err)
|
||||
return "", fmt.Errorf("failed to fetch token from authorization server: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("token: %v\n", string(res))
|
||||
return nil
|
||||
return string(res), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,14 @@ type JwtBearerFlowParams struct {
|
|||
KeyPath string
|
||||
}
|
||||
|
||||
type JwtBearerEndpoints struct {
|
||||
type JwtBearerFlowEndpoints struct {
|
||||
TrustedIssuers string
|
||||
Token string
|
||||
Clients string
|
||||
Register string
|
||||
}
|
||||
|
||||
func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (string, error) {
|
||||
func NewJwtBearerFlow(eps JwtBearerFlowEndpoints, params JwtBearerFlowParams) (string, error) {
|
||||
// 1. verify that the JWT from the issuer is valid using all keys
|
||||
var (
|
||||
idp = params.IdentityProvider
|
||||
|
|
@ -164,7 +164,7 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin
|
|||
|
||||
// 5. dynamically register new OAuth client and authorize it to make jwt_bearer request
|
||||
fmt.Printf("Registering new OAuth2 client with authorization server...\n")
|
||||
res, err = client.RegisterOAuthClient(eps.Register)
|
||||
res, err = client.RegisterOAuthClient(eps.Register, []oauth.GrantType{oauth.JwtBearer})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to register client: %v", err)
|
||||
}
|
||||
|
|
@ -189,7 +189,7 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin
|
|||
return "", fmt.Errorf("failed to delete OAuth client: %v", err)
|
||||
}
|
||||
fmt.Printf("Attempting to re-create client...\n")
|
||||
res, err := client.CreateOAuthClient(eps.Clients)
|
||||
res, err := client.CreateOAuthClient(eps.Clients, []oauth.GrantType{oauth.JwtBearer})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to register client: %v", err)
|
||||
}
|
||||
|
|
@ -210,7 +210,7 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin
|
|||
if eps.Token != "" {
|
||||
fmt.Printf("Fetching access token from authorization server...\n")
|
||||
fmt.Printf("jwt: %s\n", string(newJwt))
|
||||
res, err := client.PerformTokenGrant(eps.Token, string(newJwt))
|
||||
res, err := client.PerformJwtBearerTokenGrant(eps.Token, string(newJwt))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch access token: %v", err)
|
||||
}
|
||||
|
|
@ -237,7 +237,7 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin
|
|||
return string(res), nil
|
||||
}
|
||||
|
||||
func ForwardToken(eps JwtBearerEndpoints, params JwtBearerFlowParams) error {
|
||||
func ForwardToken(eps JwtBearerFlowEndpoints, params JwtBearerFlowParams) error {
|
||||
var (
|
||||
client = params.Client
|
||||
idToken = params.IdToken
|
||||
|
|
@ -279,7 +279,7 @@ func ForwardToken(eps JwtBearerEndpoints, params JwtBearerFlowParams) error {
|
|||
if verbose {
|
||||
fmt.Printf("Registering new OAuth2 client with authorization server...\n")
|
||||
}
|
||||
res, err := client.RegisterOAuthClient(eps.Register)
|
||||
res, err := client.RegisterOAuthClient(eps.Register, []oauth.GrantType{oauth.JwtBearer})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register client: %v", err)
|
||||
}
|
||||
|
|
@ -306,7 +306,7 @@ func ForwardToken(eps JwtBearerEndpoints, params JwtBearerFlowParams) error {
|
|||
return fmt.Errorf("failed to delete OAuth client: %v", err)
|
||||
}
|
||||
fmt.Printf("Attempting to re-create client...\n")
|
||||
res, err := client.CreateOAuthClient(eps.Clients)
|
||||
res, err := client.CreateOAuthClient(eps.Clients, []oauth.GrantType{oauth.JwtBearer})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register client: %v", err)
|
||||
}
|
||||
|
|
@ -327,7 +327,7 @@ func ForwardToken(eps JwtBearerEndpoints, params JwtBearerFlowParams) error {
|
|||
if verbose {
|
||||
fmt.Printf("Fetching access token from authorization server...\n")
|
||||
}
|
||||
res, err := client.PerformTokenGrant(eps.Token, idToken)
|
||||
res, err := client.PerformJwtBearerTokenGrant(eps.Token, idToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch access token: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,15 @@ import (
|
|||
|
||||
func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider) error {
|
||||
if config == nil {
|
||||
return fmt.Errorf("config is not valid")
|
||||
return fmt.Errorf("invalid config")
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
return fmt.Errorf("invalid client")
|
||||
}
|
||||
|
||||
if provider == nil {
|
||||
return fmt.Errorf("invalid identity provider")
|
||||
}
|
||||
|
||||
// make cache if it's not where expect
|
||||
|
|
@ -38,12 +46,13 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
|
|||
)
|
||||
|
||||
var button = MakeButton(authorizationUrl, "Login with "+client.Name)
|
||||
var jwtClient = oauth.NewClient()
|
||||
jwtClient.Scope = config.Authorization.Token.Scope
|
||||
var authzClient = oauth.NewClient()
|
||||
authzClient.Scope = config.Authorization.Token.Scope
|
||||
|
||||
// authorize oauth client and listen for callback from provider
|
||||
fmt.Printf("Waiting for authorization code redirect @%s/oidc/callback...\n", s.GetListenAddr())
|
||||
params := server.ServerParams{
|
||||
Verbose: config.Options.Verbose,
|
||||
AuthProvider: &oidc.IdentityProvider{
|
||||
Issuer: config.Authorization.Endpoints.Issuer,
|
||||
Endpoints: oidc.Endpoints{
|
||||
|
|
@ -51,14 +60,13 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
|
|||
JwksUri: config.Authorization.Endpoints.JwksUri,
|
||||
},
|
||||
},
|
||||
Verbose: config.Options.Verbose,
|
||||
JwtBearerEndpoints: flows.JwtBearerEndpoints{
|
||||
JwtBearerEndpoints: flows.JwtBearerFlowEndpoints{
|
||||
Token: config.Authorization.Endpoints.Token,
|
||||
TrustedIssuers: config.Authorization.Endpoints.TrustedIssuers,
|
||||
Register: config.Authorization.Endpoints.Register,
|
||||
},
|
||||
JwtBearerParams: flows.JwtBearerFlowParams{
|
||||
Client: jwtClient,
|
||||
Client: authzClient,
|
||||
IdentityProvider: provider,
|
||||
TrustedIssuer: &oauth.TrustedIssuer{
|
||||
AllowAnySubject: false,
|
||||
|
|
@ -70,8 +78,16 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
|
|||
Verbose: config.Options.Verbose,
|
||||
Refresh: config.Authorization.Token.Refresh,
|
||||
},
|
||||
ClientCredentialsEndpoints: flows.ClientCredentialsFlowEndpoints{
|
||||
Clients: config.Authorization.Endpoints.Clients,
|
||||
Authorize: config.Authorization.Endpoints.Authorize,
|
||||
Token: config.Authorization.Endpoints.Token,
|
||||
},
|
||||
ClientCredentialsParams: flows.ClientCredentialsFlowParams{
|
||||
Client: authzClient,
|
||||
},
|
||||
}
|
||||
err = s.Login(button, provider, client, params)
|
||||
err = s.Start(button, provider, client, params)
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
fmt.Printf("\n=========================================\nServer closed.\n=========================================\n\n")
|
||||
} else if err != nil {
|
||||
|
|
@ -79,7 +95,10 @@ func Login(config *Config, client *oauth.Client, provider *oidc.IdentityProvider
|
|||
}
|
||||
|
||||
} else if config.Options.FlowType == "client_credentials" {
|
||||
err := NewClientCredentialsFlowWithConfig(config, client)
|
||||
params := flows.ClientCredentialsFlowParams{
|
||||
Client: client,
|
||||
}
|
||||
_, err := NewClientCredentialsFlowWithConfig(config, params)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to complete client credentials flow: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,13 +72,13 @@ func NewClientWithConfigById(config *Config, id string) *oauth.Client {
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewClientCredentialsFlowWithConfig(config *Config, client *oauth.Client) error {
|
||||
func NewClientCredentialsFlowWithConfig(config *Config, params flows.ClientCredentialsFlowParams) (string, error) {
|
||||
eps := flows.ClientCredentialsFlowEndpoints{
|
||||
Create: config.Authorization.Endpoints.Clients,
|
||||
Clients: config.Authorization.Endpoints.Clients,
|
||||
Authorize: config.Authorization.Endpoints.Authorize,
|
||||
Token: config.Authorization.Endpoints.Token,
|
||||
}
|
||||
return flows.NewClientCredentialsFlow(eps, client)
|
||||
return flows.NewClientCredentialsFlow(eps, params)
|
||||
}
|
||||
|
||||
func NewServerWithConfig(conf *Config) *server.Server {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ import (
|
|||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
type GrantType = string
|
||||
|
||||
const (
|
||||
AuthorizationCode GrantType = "authorization_code"
|
||||
ClientCredentials GrantType = "client_credentials"
|
||||
JwtBearer GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
http.Client
|
||||
Id string `db:"id" yaml:"id"`
|
||||
|
|
@ -87,21 +95,25 @@ func (client *Client) GetOAuthClient(clientUrl string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (client *Client) CreateOAuthClient(registerUrl string) ([]byte, error) {
|
||||
func (client *Client) CreateOAuthClient(registerUrl string, grantTypes []GrantType) ([]byte, error) {
|
||||
// hydra endpoint: POST /clients
|
||||
if client == nil {
|
||||
return nil, fmt.Errorf("invalid client")
|
||||
}
|
||||
audience := util.QuoteArrayStrings(client.Audience)
|
||||
grantTypes = util.QuoteArrayStrings(grantTypes)
|
||||
body := httpx.Body(fmt.Sprintf(`{
|
||||
"client_id": "%s",
|
||||
"client_name": "%s",
|
||||
"client_secret": "%s",
|
||||
"token_endpoint_auth_method": "client_secret_post",
|
||||
"scope": "%s",
|
||||
"grant_types": ["urn:ietf:params:oauth:grant-type:jwt-bearer"],
|
||||
"grant_types": [%s],
|
||||
"response_types": ["token"],
|
||||
"redirect_uris": ["http://127.0.0.1:3333/callback"],
|
||||
"state": 12345678910,
|
||||
"audience": [%s]
|
||||
}`, client.Id, client.Id, client.Secret, strings.Join(client.Scope, " "), strings.Join(audience, ","),
|
||||
}`, client.Id, client.Id, client.Secret, strings.Join(client.Scope, " "), strings.Join(grantTypes, ","), strings.Join(audience, ","),
|
||||
))
|
||||
headers := httpx.Headers{
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -131,22 +143,23 @@ func (client *Client) CreateOAuthClient(registerUrl string) ([]byte, error) {
|
|||
return b, err
|
||||
}
|
||||
|
||||
func (client *Client) RegisterOAuthClient(registerUrl string) ([]byte, error) {
|
||||
func (client *Client) RegisterOAuthClient(registerUrl string, grantTypes []GrantType) ([]byte, error) {
|
||||
// hydra endpoint: POST /oauth2/register
|
||||
if registerUrl == "" {
|
||||
return nil, fmt.Errorf("no URL provided")
|
||||
}
|
||||
audience := util.QuoteArrayStrings(client.Audience)
|
||||
grantTypes = util.QuoteArrayStrings(grantTypes)
|
||||
body := httpx.Body(fmt.Sprintf(`{
|
||||
"client_name": "opaal",
|
||||
"token_endpoint_auth_method": "client_secret_post",
|
||||
"scope": "%s",
|
||||
"grant_types": ["urn:ietf:params:oauth:grant-type:jwt-bearer"],
|
||||
"grant_types": [%s],
|
||||
"response_types": ["token"],
|
||||
"redirect_uris": ["http://127.0.0.1:3333/callback"],
|
||||
"state": 12345678910,
|
||||
"audience": [%s]
|
||||
}`, strings.Join(client.Scope, " "), strings.Join(audience, ","),
|
||||
}`, strings.Join(client.Scope, " "), strings.Join(grantTypes, ","), strings.Join(audience, ","),
|
||||
))
|
||||
headers := httpx.Headers{
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -196,7 +209,7 @@ func (client *Client) AuthorizeOAuthClient(authorizeUrl string) ([]byte, error)
|
|||
return b, nil
|
||||
}
|
||||
|
||||
func (client *Client) PerformTokenGrant(clientUrl string, encodedJwt string) ([]byte, error) {
|
||||
func (client *Client) PerformJwtBearerTokenGrant(clientUrl string, encodedJwt string) ([]byte, error) {
|
||||
// hydra endpoint: /oauth/token
|
||||
body := "grant_type=" + url.QueryEscape("urn:ietf:params:oauth:grant-type:jwt-bearer") +
|
||||
"&client_id=" + client.Id +
|
||||
|
|
@ -220,8 +233,29 @@ func (client *Client) PerformTokenGrant(clientUrl string, encodedJwt string) ([]
|
|||
|
||||
}
|
||||
|
||||
// set flow ID back to empty string to indicate a completed flow
|
||||
client.FlowId = ""
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (client *Client) PerformClientCredentialsTokenGrant(clientUrl string) ([]byte, error) {
|
||||
// hydra endpoint: /oauth/token
|
||||
body := "grant_type=" + url.QueryEscape("client_credentials") +
|
||||
"&client_id=" + client.Id +
|
||||
"&client_secret=" + client.Secret +
|
||||
"&redirect_uri=" + url.QueryEscape("http://127.0.0.1:3333/callback")
|
||||
// add optional params if valid
|
||||
if client.Scope != nil || len(client.Scope) > 0 {
|
||||
body += "&scope=" + strings.Join(client.Scope, "+")
|
||||
}
|
||||
headers := httpx.Headers{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Authorization": "Bearer " + client.RegistrationAccessToken,
|
||||
}
|
||||
|
||||
_, b, err := httpx.MakeHttpRequest(clientUrl, http.MethodPost, []byte(body), headers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make HTTP request: %v", err)
|
||||
|
||||
}
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ type Server struct {
|
|||
type ServerParams struct {
|
||||
AuthProvider *oidc.IdentityProvider
|
||||
Verbose bool
|
||||
JwtBearerEndpoints flows.JwtBearerEndpoints
|
||||
ClientCredentialsEndpoints flows.ClientCredentialsFlowEndpoints
|
||||
ClientCredentialsParams flows.ClientCredentialsFlowParams
|
||||
JwtBearerEndpoints flows.JwtBearerFlowEndpoints
|
||||
JwtBearerParams flows.JwtBearerFlowParams
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +40,7 @@ func (s *Server) GetListenAddr() string {
|
|||
return fmt.Sprintf("%s:%d", s.Host, s.Port)
|
||||
}
|
||||
|
||||
func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *oauth.Client, params ServerParams) error {
|
||||
func (s *Server) Start(buttons string, provider *oidc.IdentityProvider, client *oauth.Client, params ServerParams) error {
|
||||
var target = ""
|
||||
|
||||
// check if callback is set
|
||||
|
|
@ -114,14 +116,10 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *
|
|||
w.Write(jwks)
|
||||
|
||||
})
|
||||
r.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request) {
|
||||
r.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
|
||||
// use refresh token provided to do a refresh token grant
|
||||
refreshToken := r.URL.Query().Get("refresh-token")
|
||||
if refreshToken == "" {
|
||||
fmt.Printf("no refresh token provided")
|
||||
http.Redirect(w, r, "/error", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if refreshToken != "" {
|
||||
_, err := params.JwtBearerParams.Client.PerformRefreshTokenGrant(provider.Endpoints.Token, refreshToken)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to perform refresh token grant: %v\n", err)
|
||||
|
|
@ -138,6 +136,17 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *
|
|||
if err != nil {
|
||||
fmt.Printf("failed to make request")
|
||||
http.Redirect(w, r, "/error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// perform a client credentials grant and return a token
|
||||
var err error
|
||||
accessToken, err = flows.NewClientCredentialsFlow(params.ClientCredentialsEndpoints, params.ClientCredentialsParams)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to perform client credentials flow: %v\n", err)
|
||||
http.Redirect(w, r, "/error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
r.HandleFunc(s.Callback, func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -196,6 +205,13 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *
|
|||
if params.Verbose {
|
||||
fmt.Printf("Serving success page.\n")
|
||||
}
|
||||
|
||||
// return only the token with no web page if "no-browser" header is set
|
||||
noBrowser := r.Header.Get("no-browser")
|
||||
if noBrowser != "" {
|
||||
return
|
||||
}
|
||||
|
||||
template, err := gonja.FromFile("pages/success.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue