Refactored and added client credentials flow

This commit is contained in:
David Allen 2024-02-29 20:14:53 -07:00
parent f912890a2d
commit f490eb4fc4
No known key found for this signature in database
GPG key ID: 1D2A29322FBB6FCB
9 changed files with 113 additions and 41 deletions

View file

@ -1,7 +1,7 @@
package cmd package cmd
import ( import (
opaal "davidallendj/opaal/internal" "davidallendj/opaal/internal/flows"
"fmt" "fmt"
"os" "os"
@ -13,7 +13,7 @@ var loginCmd = &cobra.Command{
Short: "Start the login flow", Short: "Start the login flow",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
for { for {
err := opaal.Login(&config) err := flows.Login(&config)
if err != nil { if err != nil {
fmt.Printf("%v\n", err) fmt.Printf("%v\n", err)
os.Exit(1) os.Exit(1)
@ -37,5 +37,6 @@ func init() {
loginCmd.Flags().BoolVar(&config.DecodeIdToken, "decode-id-token", config.DecodeIdToken, "decode and print ID token from identity provider") loginCmd.Flags().BoolVar(&config.DecodeIdToken, "decode-id-token", config.DecodeIdToken, "decode and print ID token from identity provider")
loginCmd.Flags().BoolVar(&config.DecodeAccessToken, "decore-access-token", config.DecodeAccessToken, "decode and print access token from authorization server") loginCmd.Flags().BoolVar(&config.DecodeAccessToken, "decore-access-token", config.DecodeAccessToken, "decode and print access token from authorization server")
loginCmd.Flags().BoolVar(&config.RunOnce, "once", config.RunOnce, "set whether to run login once and exit") loginCmd.Flags().BoolVar(&config.RunOnce, "once", config.RunOnce, "set whether to run login once and exit")
loginCmd.Flags().StringVar(&config.GrantType, "grant-type", config.GrantType, "set the grant-type/authorization flow")
rootCmd.AddCommand(loginCmd) rootCmd.AddCommand(loginCmd)
} }

View file

@ -21,6 +21,18 @@ var rootCmd = &cobra.Command{
}, },
} }
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "failed to start CLI: %s", err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&configPath, "config", "", "set the config path")
}
func initConfig() { func initConfig() {
// load config if found or create a new one // load config if found or create a new one
if configPath != "" { if configPath != "" {
@ -35,15 +47,3 @@ func initConfig() {
} }
} }
} }
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "failed to start CLI: %s", err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&configPath, "config", "", "set the config path")
}

View file

@ -16,6 +16,8 @@ urls:
jwks_uri: http://git.towk.local:3000/login/oauth/keys jwks_uri: http://git.towk.local:3000/login/oauth/keys
login: http://127.0.0.1:4433/self-service/login/api login: http://127.0.0.1:4433/self-service/login/api
login-flow-id: http://127.0.0.1:4433/self-service/login/flows?id={id} login-flow-id: http://127.0.0.1:4433/self-service/login/flows?id={id}
register-client: http://127.0.0.1:4445/clients
authorize-client: http://127.0.0.1:4444/oauth2/authorize
state: "" state: ""
response-type: code response-type: code
decode-id-token: true decode-id-token: true

View file

@ -129,12 +129,17 @@ func (client *Client) FetchCSRFToken(flowUrl string) error {
func (client *Client) FetchTokenFromAuthenticationServer(code string, remoteUrl string, state string) ([]byte, error) { func (client *Client) FetchTokenFromAuthenticationServer(code string, remoteUrl string, state string) ([]byte, error) {
data := url.Values{ data := url.Values{
"grant_type": {"authorization_code"}, "grant_type": {"authorization_code"},
"code": {code},
"client_id": {client.Id}, "client_id": {client.Id},
"client_secret": {client.Secret}, "client_secret": {client.Secret},
"state": {state},
"redirect_uri": {strings.Join(client.RedirectUris, ",")}, "redirect_uri": {strings.Join(client.RedirectUris, ",")},
} }
// add optional params if valid
if code != "" {
data["code"] = []string{code}
}
if state != "" {
data["state"] = []string{state}
}
res, err := http.PostForm(remoteUrl, data) res, err := http.PostForm(remoteUrl, data)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get ID token: %s", err) return nil, fmt.Errorf("failed to get ID token: %s", err)
@ -151,9 +156,16 @@ func (client *Client) FetchTokenFromAuthorizationServer(remoteUrl string, jwt st
// hydra endpoint: /oauth/token // hydra endpoint: /oauth/token
data := "grant_type=" + url.QueryEscape("urn:ietf:params:oauth:grant-type:jwt-bearer") + data := "grant_type=" + url.QueryEscape("urn:ietf:params:oauth:grant-type:jwt-bearer") +
"&client_id=" + client.Id + "&client_id=" + client.Id +
"&client_secret=" + client.Secret + "&client_secret=" + client.Secret
"&scope=" + strings.Join(scope, "+") +
"&assertion=" + jwt // add optional params if valid
if jwt != "" {
data += "&assertion=" + jwt
}
if scope != nil || len(scope) > 0 {
data += "&scope=" + strings.Join(scope, "+")
}
fmt.Printf("encoded params: %v\n\n", data) fmt.Printf("encoded params: %v\n\n", data)
req, err := http.NewRequest("POST", remoteUrl, bytes.NewBuffer([]byte(data))) req, err := http.NewRequest("POST", remoteUrl, bytes.NewBuffer([]byte(data)))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

View file

@ -48,6 +48,11 @@ func NewConfig() Config {
AccessToken: "", AccessToken: "",
TrustedIssuers: "", TrustedIssuers: "",
ServerConfig: "", ServerConfig: "",
JwksUri: "",
Login: "",
LoginFlowId: "",
RegisterClient: "",
AuthorizeClient: "",
}, },
OpenBrowser: false, OpenBrowser: false,
DecodeIdToken: false, DecodeIdToken: false,

View file

@ -1,6 +1,7 @@
package opaal package flows
import ( import (
opaal "davidallendj/opaal/internal"
"davidallendj/opaal/internal/oidc" "davidallendj/opaal/internal/oidc"
"encoding/json" "encoding/json"
"errors" "errors"
@ -12,15 +13,7 @@ import (
"github.com/davidallendj/go-utils/util" "github.com/davidallendj/go-utils/util"
) )
func Login(config *Config) error { func AuthorizationCode(config *opaal.Config, server *opaal.Server, client *opaal.Client) error {
if config == nil {
return fmt.Errorf("config is not valid")
}
// initialize client that will be used throughout login flow
server := NewServerWithConfig(config)
client := NewClientWithConfig(config)
// initiate the login flow and get a flow ID and CSRF token // initiate the login flow and get a flow ID and CSRF token
{ {
err := client.InitiateLoginFlow(config.ActionUrls.Login) err := client.InitiateLoginFlow(config.ActionUrls.Login)
@ -49,7 +42,7 @@ func Login(config *Config) error {
} }
// check if all appropriate parameters are set in config // check if all appropriate parameters are set in config
if !HasRequiredParams(config) { if !opaal.HasRequiredParams(config) {
return fmt.Errorf("client ID must be set") return fmt.Errorf("client ID must be set")
} }

View file

@ -0,0 +1,29 @@
package flows
import (
opaal "davidallendj/opaal/internal"
"fmt"
)
func ClientCredentials(config *opaal.Config, server *opaal.Server, client *opaal.Client) error {
// register a new OAuth 2 client with authorization srever
_, err := client.RegisterOAuthClient(config.ActionUrls.RegisterClient, nil)
if err != nil {
return fmt.Errorf("failed to register OAuth client: %v", err)
}
// authorize the client
_, err = client.AuthorizeClient(config.ActionUrls.AuthorizeClient)
if err != nil {
return fmt.Errorf("failed to authorize client: %v", err)
}
// request a token from the authorization server
res, err := client.FetchTokenFromAuthorizationServer(config.ActionUrls.AccessToken, "", nil)
if err != nil {
return fmt.Errorf("failed to fetch token from authorization server: %v", err)
}
fmt.Printf("token: %v\n", string(res))
return nil
}

28
internal/flows/login.go Normal file
View file

@ -0,0 +1,28 @@
package flows
import (
opaal "davidallendj/opaal/internal"
"fmt"
)
func Login(config *opaal.Config) error {
if config == nil {
return fmt.Errorf("config is not valid")
}
// initialize client that will be used throughout login flow
server := opaal.NewServerWithConfig(config)
client := opaal.NewClientWithConfig(config)
fmt.Printf("grant type: %v\n", config.GrantType)
if config.GrantType == "authorization_code" {
AuthorizationCode(config, server, client)
} else if config.GrantType == "client_credentials" {
ClientCredentials(config, server, client)
} else {
return fmt.Errorf("invalid grant type")
}
return nil
}

View file

@ -8,6 +8,8 @@ type ActionUrls struct {
JwksUri string `yaml:"jwks_uri"` JwksUri string `yaml:"jwks_uri"`
Login string `yaml:"login"` Login string `yaml:"login"`
LoginFlowId string `yaml:"login-flow-id"` LoginFlowId string `yaml:"login-flow-id"`
RegisterClient string `yaml:"register-client"`
AuthorizeClient string `yaml:"authorize-client"`
} }
func HasRequiredParams(config *Config) bool { func HasRequiredParams(config *Config) bool {