diff --git a/proto/oidc/cfg.go b/proto/oidc/cfg.go index f134c12..d7b66b4 100644 --- a/proto/oidc/cfg.go +++ b/proto/oidc/cfg.go @@ -8,6 +8,7 @@ type ServerProfile struct { Form *HTMLFormConfig `yaml:"form,omitempty" mapstructure:"form,omitempty"` Insecure bool PasswordGrant *bool `yaml:"passwordgrant,omitempty"` + RefreshOnly *bool `yaml:"refreshonly,omitempty"` AdditionalScopes []string `yaml:"additionalscopes,omitempty"` } @@ -21,6 +22,10 @@ func (s ServerProfile) Merge(in ServerProfile) ServerProfile { if ret.PasswordGrant == nil { ret.PasswordGrant = in.PasswordGrant } + ret.RefreshOnly = s.RefreshOnly + if ret.RefreshOnly == nil { + ret.RefreshOnly = in.RefreshOnly + } ret.Form = s.Form if ret.Form == nil { ret.Form = in.Form diff --git a/proto/oidc/cmd.go b/proto/oidc/cmd.go index b60060d..01a993d 100644 --- a/proto/oidc/cmd.go +++ b/proto/oidc/cmd.go @@ -42,10 +42,10 @@ var oidcConnectWizard = []proto.SetupStep{ oidcCfg.Cfg.CallbackURL = in return nil }, GetDefault: func(remoteCfg interface{}) string { return remoteCfg.(*Config).CallbackURL }}, - {Prompt: "OIDC flow (auth - authorization code flow, pwd - password grant flow, leave empty to use server profile default):", + {Prompt: "OIDC flow (auth - authorization code flow, pwd - password grant flow, refresh - refresh grant flow, leave empty to use server profile default):", Parse: func(in string) error { in = strings.TrimSpace(in) - if in == "pwd" || in == "auth" || in == "" { + if in == "pwd" || in == "auth" || in == "refresh" || in == "" { oidcCfg.flow = in } else { return fmt.Errorf("Invalid entry %s, enter auth, pwd,or leave empty", in) @@ -74,7 +74,7 @@ func init() { cmd.Flags().StringVarP(&oidcCfg.Cfg.TokenAPI, "token-api", "a", "", "Token API (defaults to protocol/openid-connect/token)") cmd.Flags().StringVarP(&oidcCfg.Cfg.AuthAPI, "auth-api", "t", "", "Auth API (defaults to protocol/openid-connect/auth)") cmd.Flags().StringVarP(&oidcCfg.scopes, "scopes", "o", "", "Additional scopes to request from server (-o scope1,scope2,scope3)") - cmd.Flags().StringVarP(&oidcCfg.flow, "flow", "f", "", "Use authorization code flow (auth) or password grant flow (pwd)") + cmd.Flags().StringVarP(&oidcCfg.flow, "flow", "f", "", "Use authorization code flow (auth), password grant flow (pwd), or refresh token flow (refresh)") if cfg.InsecureAllowed() { cmd.Flags().BoolVarP(&oidcCfg.Cfg.Insecure, "insecure", "k", false, "Do not validate server certificates") } @@ -165,14 +165,25 @@ func parseOidc(c *cobra.Command) { } } } - if oidcCfg.flow == "auth" { + switch oidcCfg.flow { + case "auth": x := false oidcCfg.Cfg.PasswordGrant = &x - } else if oidcCfg.flow == "pwd" { + oidcCfg.Cfg.RefreshOnly = &x + case "pwd": x := true + y := false oidcCfg.Cfg.PasswordGrant = &x - } else if oidcCfg.flow != "" { - log.Fatalf("Invalid flow: %s Use 'auth' or 'pwd'", oidcCfg.flow) + oidcCfg.Cfg.RefreshOnly = &y + case "refresh": + x := false + y := true + oidcCfg.Cfg.PasswordGrant = &x + oidcCfg.Cfg.RefreshOnly = &y + case "": + break + default: + log.Fatalf("Invalid flow: %s Use 'auth', 'pwd', or 'refresh'", oidcCfg.flow) } var formCfg HTMLFormConfig diff --git a/proto/oidc/protocol.go b/proto/oidc/protocol.go index d4614c6..1dbe408 100644 --- a/proto/oidc/protocol.go +++ b/proto/oidc/protocol.go @@ -123,7 +123,7 @@ func (p *Protocol) GetToken(request proto.TokenRequest) (string, interface{}, er } if userName == "" { - log.Fatalf("Username is required for oidc quth") + log.Fatalf("Username is required for oidc auth") return "", nil, nil } var tok *TokenData @@ -184,7 +184,14 @@ func (p *Protocol) GetToken(request proto.TokenRequest) (string, interface{}, er ctx = context.WithValue(ctx, oauth2.HTTPClient, proto.GetHTTPClient()) conf.Scopes = append(conf.Scopes, config.AdditionalScopes...) log.Debugf("Password grant: %v", config.PasswordGrant) - if config.PasswordGrant != nil && *config.PasswordGrant { + if config.RefreshOnly != nil && *config.RefreshOnly { + tok.RefreshToken = cfg.AskPasswordWithPrompt(fmt.Sprintf("Refresh token for %s: ", userName)) + err := p.Refresh(tok, serverData) + if err != nil { + return "", nil, err + } + return tok.FormatToken(request.Out), p.Tokens, nil + } else if config.PasswordGrant != nil && *config.PasswordGrant { var password string if len(request.Password) > 0 { password = request.Password diff --git a/readme.md b/readme.md index 66b7b3a..6a98b2d 100644 --- a/readme.md +++ b/readme.md @@ -97,11 +97,22 @@ Took supports direct access grants. In this flow, took asks username and passwor them to the authentication server. ``` - took add oidc -n prod-direct -c 12345 -s abcdef -u https://myserver/realms/myrealm -p + took add oidc -n prod-direct -c 12345 -s abcdef -u https://myserver/realms/myrealm -f pwd ``` To use this, the authentication server must be configured to support direct access grants flow for this client. +## Refresh Token Flow + +Took supports using only refresh token grants. In this flow, took asks for a refresh token +to send to the authentication server. This is commonly used in conjunction with +offline refresh tokens. + +``` + took add oidc -n prod-refresh -c 12345 -u https://myserver/realms/myrealm -f refresh +``` +To use this, you must already have obtained a refresh token via some other means +(usually from a web portal). # Multiple users