diff --git a/README.md b/README.md index 67c7aa3..f0ce0e4 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,32 @@ Import it in your code: import oidcauth "github.com/TJM/gin-gonic-oidcauth" ``` -## Example +Use it: (see complete [examples](example)) + +```go + // NOTE: oidcauth *requires* sessions *before* oidcauth + // SEE Examples to see how. + + // Authentication Config + auth, err := oidcauth.GetOidcAuth(oidcauth.DefaultConfig()) + if err != nil { + panic("auth setup failed") + } + router.GET("/login", auth.Login) // Unnecessary, as requesting a "AuthRequired" resource will initiate login, but potentially convenient + router.GET("/callback", auth.AuthCallback) + router.GET("/logout", auth.Logout) + + // Private Route Group... + private := r.Group("/private", auth.AuthRequired()) + { + private.GET("", func(c *gin.Context) { + c.String(http.StatusOK, "Private!") + } + // ... + } +``` + +## Examples Prerequisites: @@ -30,85 +55,60 @@ Prerequisites: * [Google](https://developers.google.com/identity/protocols/oauth2/openid-connect) * [GitHub](https://plugins.miniorange.com/oauth-openid-login-using-github) * etc - -The examples below will use Google Accounts. See: [go-oidc examples readme](https://github.com/coreos/go-oidc/tree/v3/example) -* Sessions example: [example/main.go](example/main.go) +### DEX Identity Provider -```go -package main +The example below will use [DEX IdP](https://dexidp.io/). Please clone their repo and start DEX in a separate window. + +* Start DEX IdP: + +```console +./bin/dex serve examples/config-dev.yaml +``` -import ( - "fmt" - "net/http" - "os" +* Start [DEX ExampleApp(example/dex/main.go)]: - oidcauth "github.com/TJM/gin-gonic-oidcauth" - "github.com/gin-contrib/sessions" - "github.com/gin-contrib/sessions/cookie" - "github.com/gin-gonic/gin" -) +```console +go run example/dex/main.go +``` -func main() { - r := gin.Default() +* Visit: + * Attempt to access something "private" + * Login: + * Logout: - // Session Config (Basic cookies) - store := cookie.NewStore([]byte("secret"), nil) // Do not use "secret", nil in production. This sets the keypairs for auth, encryption of the cookies. - r.Use(sessions.Sessions("mysession", store)) // Sessions must be Use(d) before oidcauth, as oidcauth requires sessions +### Google Accounts Identity Provider - // NOTE: DefaultConfig uses Google Accounts - // - See https://github.com/coreos/go-oidc/blob/v3/example/README.md - auth, err := oidcauth.GetOidcAuth(oidcauth.DefaultConfig()) - if err != nil { - panic("auth setup failed") - } - if os.Getenv("DEBUG") != "" { - auth.Debug = true - } +The example below will use Google Accounts. See: [go-oidc examples readme](https://github.com/coreos/go-oidc/tree/v3/example). - r.GET("/login", auth.Login) // Unnecessary, as requesting a "AuthRequired" resource will initiate login, but potentially convenient - r.GET("/auth/google/callback", auth.AuthCallback) - r.GET("/logout", auth.Logout) - - // Allow access to / for unauthenticated users, but authenticated users will be greated by name. - r.GET("/", func(c *gin.Context) { - session := sessions.Default(c) - name := "world" - n := session.Get("name") - if n != nil { - name = n.(string) - } - // session.Save() // if it has been changed, which it has not - c.String(http.StatusOK, fmt.Sprintf("Hello, %s.", name)) - }) +NOTE: This example used port `5556` to be compatible with the other go-oidc examples, but it will clash with "dex" which runs on the same port by default. - private := r.Group("/private", auth.AuthRequired()) - { - private.GET("", func(c *gin.Context) { - var name, email, out string - login := c.GetString(oidcauth.AuthUserKey) - session := sessions.Default(c) - n := session.Get("name") - if n == nil { - name = "Someone without a name?" - } else { - name = n.(string) - } - e := session.Get("email") - if e != nil { - email = e.(string) - } - out = fmt.Sprintf("Hello, %s <%s>.\nLogin: %s\n", name, email, login) - // session.Save() // if it has been changed, which it has not - c.String(http.StatusOK, out) - return - }) - } +* Setup Google + + 1. Visit your [Google Developer Console][google-developer-console]. + 2. Click "Credentials" on the left column. + 3. Click the "Create credentials" button followed by "OAuth client ID". + 4. Select "Web application" and add "http://127.0.0.1:5556/auth/google/callback" as an authorized redirect URI. + 5. Click create and add the printed client ID and secret to your environment using the following variables: - r.Run(":5556") -} + ```bash + export GOOGLE_OAUTH2_CLIENT_ID= + export GOOGLE_OAUTH2_CLIENT_SECRET= + ``` + +* Start Google Example [example/google/main.go](example/google/main.go): + +```console +go run example/google/main.go ``` +* Visit: + * Attempt to access something "private" + * Login: + * Logout: + +[google-developer-console]: https://console.developers.google.com/apis/dashboard + ## License Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). diff --git a/config.go b/config.go index ffee646..c9c91b2 100644 --- a/config.go +++ b/config.go @@ -19,11 +19,11 @@ type Config struct { ClientSecret string // IssuerURL is the root URL to theIdentity Provider - // Default value is: "https://accounts.google.com" + // Default value is: (read from OS ENV: OIDC_ISSUER_URL) IssuerURL string // RedirectURL is the path that the Identity Provider will redirect clients to - // Default value is: "http://127.0.0.1:5556/auth/google/callback" + // Default value is: (read from OS ENV: OIDC_REDIRECT_URL) RedirectURL string // Scopes is a list of OIDC Scopes to request. @@ -60,10 +60,10 @@ type Config struct { // NOTE: This matches the examples on https://github.com/coreos/go-oidc/tree/v3/example func DefaultConfig() (c *Config) { c = &Config{ - ClientID: os.Getenv("GOOGLE_OAUTH2_CLIENT_ID"), - ClientSecret: os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET"), - IssuerURL: "https://accounts.google.com", - RedirectURL: "http://127.0.0.1:5556/auth/google/callback", + ClientID: os.Getenv("OIDC_CLIENT_ID"), + ClientSecret: os.Getenv("OIDC_CLIENT_SECRET"), + IssuerURL: os.Getenv("OIDC_ISSUER_URL"), + RedirectURL: os.Getenv("OIDC_REDIRECT_URL"), Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, LoginClaim: "email", SessionClaims: []string{"*"}, @@ -73,6 +73,28 @@ func DefaultConfig() (c *Config) { return } +// ExampleConfigDex will return the config for a default DEX IdP example-app +// DEX: https://github.com/dexidp/dex +func ExampleConfigDex() (c *Config) { + c = DefaultConfig() + c.ClientID = "example-app" + c.ClientSecret = "ZXhhbXBsZS1hcHAtc2VjcmV0" + c.RedirectURL = "http://127.0.0.1:5555/callback" + c.IssuerURL = "http://127.0.0.1:5556/dex" + return +} + +// ExampleConfigGoogle will return the config for the Google Accounts IdP like the go-oidc examples +// go-oidc google example: https://github.com/coreos/go-oidc/tree/v3/example +func ExampleConfigGoogle() (c *Config) { + c = DefaultConfig() + c.ClientID = os.Getenv("GOOGLE_OAUTH2_CLIENT_ID") + c.ClientSecret = os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET") + c.RedirectURL = "http://127.0.0.1:5556/auth/google/callback" + c.IssuerURL = "https://accounts.google.com" + return +} + // Validate will validate the Config func (c Config) Validate() (err error) { diff --git a/example/dex/main.go b/example/dex/main.go new file mode 100644 index 0000000..d36dd02 --- /dev/null +++ b/example/dex/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "net/http" + + oidcauth "github.com/TJM/gin-gonic-oidcauth" + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + // Session Config (Basic cookies) + store := cookie.NewStore([]byte("secret"), nil) // Do not use "secret", nil in production. This sets the keypairs for auth, encryption of the cookies. + r.Use(sessions.Sessions("oidcauth-example", store)) // Sessions must be Use(d) before oidcauth, as oidcauth requires sessions + + // Authentication Config - Uses example dex config + // - https://dexidp.io/docs/getting-started/ + auth, err := oidcauth.GetOidcAuth(oidcauth.ExampleConfigDex()) + if err != nil { + panic("auth setup failed") + } + r.GET("/login", auth.Login) // Unnecessary, as requesting a "AuthRequired" resource will initiate login, but potentially convenient + r.GET("/callback", auth.AuthCallback) + r.GET("/logout", auth.Logout) + + // Allow access to / for unauthenticated users, but authenticated users will be greated by name. + r.GET("/", func(c *gin.Context) { + session := sessions.Default(c) + name := "world" + n := session.Get("name") + if n != nil { + name = n.(string) + } + // session.Save() // if it has been changed, which it has not + c.String(http.StatusOK, fmt.Sprintf("Hello, %s.", name)) + }) + + private := r.Group("/private", auth.AuthRequired()) + { + private.GET("", func(c *gin.Context) { + var name, email, out string + login := c.GetString(oidcauth.AuthUserKey) + session := sessions.Default(c) + n := session.Get("name") + if n == nil { + name = "Someone without a name?" + } else { + name = n.(string) + } + e := session.Get("email") + if e != nil { + email = e.(string) + } + out = fmt.Sprintf("Hello, %s <%s>.\nLogin: %s\n", name, email, login) + // session.Save() // if it has been changed, which it has not + c.String(http.StatusOK, out) + return + }) + } + + r.Run(":5555") +} diff --git a/example/main.go b/example/google/main.go similarity index 82% rename from example/main.go rename to example/google/main.go index 84c6e86..e2fcfea 100644 --- a/example/main.go +++ b/example/google/main.go @@ -15,12 +15,12 @@ func main() { r := gin.Default() // Session Config (Basic cookies) - store := cookie.NewStore([]byte("secret"), nil) // Do not use "secret", nil in production. This sets the keypairs for auth, encryption of the cookies. - r.Use(sessions.Sessions("mysession", store)) // Sessions must be Use(d) before oidcauth, as oidcauth requires sessions + store := cookie.NewStore([]byte("secret"), nil) // Do not use "secret", nil in production. This sets the keypairs for auth, encryption of the cookies. + r.Use(sessions.Sessions("oidcauth-example", store)) // Sessions must be Use(d) before oidcauth, as oidcauth requires sessions // NOTE: DefaultConfig uses Google Accounts // - See https://github.com/coreos/go-oidc/blob/v3/example/README.md - auth, err := oidcauth.GetOidcAuth(oidcauth.DefaultConfig()) + auth, err := oidcauth.GetOidcAuth(oidcauth.ExampleConfigGoogle()) if err != nil { panic("auth setup failed") }