Okta Integration - Bring Okta OAuth as an available option (#128)

- Edited package.json for react bug
 	+ https://github.com/ReactTraining/react-router/issues/6630
This commit is contained in:
sliptripfall 2019-03-20 02:28:01 -05:00 committed by Max Schmitt
parent ea5f8c888e
commit 63d502fce1
8 changed files with 109 additions and 6 deletions

View File

@ -15,7 +15,7 @@
- Expirable Links
- URL deletion
- Multiple authorization strategies:
- Local authorization via OAuth 2.0 (Google, GitHub and Microsoft)
- Local authorization via OAuth 2.0 (Google, GitHub, Microsoft, and Okta)
- Proxy authorization for running behind e.g. [Google IAP](https://cloud.google.com/iap/)
- Easy [ShareX](https://github.com/ShareX/ShareX) integration
- Dockerizable

View File

@ -1,5 +1,5 @@
ListenAddr: ':8080' # Consists of 'IP:Port', e.g. ':8080' listens on any IP and on Port 8080
BaseURL: 'http://localhost:3000' # Origin URL, required for the authentication via OAuth callback
BaseURL: 'http://localhost:8080' # Origin URL, required for the authentication via OAuth callback
DisplayURL: '' # (OPTIONAL) Display URL, how the apication will present itself in the UI - if not set, defaults to BaseURL
Backend: boltdb # Can be 'boltdb' or 'redis'
DataDir: ./data # Contains: the database and the private key
@ -10,14 +10,18 @@ ShortedIDLength: 10 # Length of the random generated ID which is used for new sh
AuthBackend: oauth # Can be 'oauth' or 'proxy'
Google: # only relevant when using the oauth authbackend
ClientID: replace me
ClientSecret: replace me
ClientSecret: 'replace me'
GitHub: # only relevant when using the oauth authbackend
ClientID: replace me
ClientSecret: replace me
ClientSecret: 'replace me'
EndpointURL: # (OPTIONAL) URL for custom endpoint (currently only for github); e.g. 'https://github.mydomain.com'
Microsoft: # only relevant when using the oauth authbackend
ClientID: replace me
ClientSecret: 'replace me'
Okta: # only relevant when using the oauth authbackend
ClientID: replace me
ClientSecret: 'replace me'
EndpointURL: # (MANDATORY) Issuer URL from the OAuth API => Authorization Servers in Okta
Proxy: # only relevant when using the proxy authbackend
RequireUserHeader: false # If true, will reject connections that do not have the UserHeader set
UserHeader: "X-Goog-Authenticated-User-ID" # pull the unique user ID from this header

View File

@ -41,6 +41,11 @@ func (h *Handler) initOAuth() {
auth.WithAdapterWrapper(auth.NewMicrosoftAdapter(microsoft.ClientID, microsoft.ClientSecret), h.engine.Group("/api/v1/auth/microsoft"))
h.providers = append(h.providers, "microsoft")
}
okta := util.GetConfig().Okta
if okta.Enabled() {
auth.WithAdapterWrapper(auth.NewOktaAdapter(okta.ClientID, okta.ClientSecret, okta.EndpointURL), h.engine.Group("/api/v1/auth/okta"))
h.providers = append(h.providers, "okta")
}
h.engine.POST("/api/v1/auth/check", h.handleAuthCheck)
}

View File

@ -0,0 +1,84 @@
package auth
import (
"context"
"encoding/json"
"net/url"
"strings"
"github.com/mxschmitt/golang-url-shortener/internal/util"
"github.com/sirupsen/logrus"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)
type oktaAdapter struct {
config *oauth2.Config
}
// NewOktaAdapter creates an oAuth adapter out of the credentials and the baseURL
func NewOktaAdapter(clientID, clientSecret, endpointURL string) Adapter {
if endpointURL == "" {
logrus.Error("Configure Okta Endpoint")
}
return &oktaAdapter{&oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: util.GetConfig().BaseURL + "/api/v1/auth/okta/callback",
Scopes: []string{
"profile",
"openid",
"offline_access",
},
Endpoint: oauth2.Endpoint{
AuthURL: endpointURL + "/v1/authorize",
TokenURL: endpointURL + "/v1/token",
},
}}
}
func (a *oktaAdapter) GetRedirectURL(state string) string {
return a.config.AuthCodeURL(state)
}
func (a *oktaAdapter) GetUserData(state, code string) (*user, error) {
logrus.Debugf("Getting User Data with state: %s, and code: %s", state, code)
oAuthToken, err := a.config.Exchange(context.Background(), code)
if err != nil {
return nil, errors.Wrap(err, "could not exchange code")
}
if util.GetConfig().Okta.EndpointURL == "" {
logrus.Error("Okta EndpointURL is Empty")
}
oktaUrl, err := url.Parse(util.GetConfig().Okta.EndpointURL)
if err != nil {
return nil, errors.Wrap(err, "could not parse Okta EndpointURL")
}
oktaBaseURL := strings.Replace(oktaUrl.String(), oktaUrl.RequestURI(), "", 1)
oAuthUserInfoReq, err := a.config.Client(context.Background(), oAuthToken).Get(oktaBaseURL + "/api/v1/users/me")
if err != nil {
return nil, errors.Wrap(err, "could not get user data")
}
defer oAuthUserInfoReq.Body.Close()
var oUser struct {
ID int `json:"sub"`
// Custom URL property for user Avatar can go here
Name string `json:"name"`
}
if err = json.NewDecoder(oAuthUserInfoReq.Body).Decode(&oUser); err != nil {
return nil, errors.Wrap(err, "decoding user info failed")
}
return &user{
ID: string(oUser.ID),
Name: oUser.Name,
Picture: util.GetConfig().BaseURL + "/images/okta_logo.png", // Default Okta Avatar
}, nil
}
func (a *oktaAdapter) GetOAuthProviderName() string {
return "okta"
}

View File

@ -28,6 +28,7 @@ type Configuration struct {
Google oAuthConf `yaml:"Google" env:"GOOGLE"`
GitHub oAuthConf `yaml:"GitHub" env:"GITHUB"`
Microsoft oAuthConf `yaml:"Microsoft" env:"MICROSOFT"`
Okta oAuthConf `yaml:"Okta" env:"OKTA"`
Proxy proxyAuthConf `yaml:"Proxy" env:"PROXY"`
Redis redisConf `yaml:"Redis" env:"REDIS"`
}
@ -46,7 +47,7 @@ type redisConf struct {
type oAuthConf struct {
ClientID string `yaml:"ClientID" env:"CLIENT_ID"`
ClientSecret string `yaml:"ClientSecret" env:"CLIENT_SECRET"`
EndpointURL string `yaml:"EndPointURL" env:"ENDPOINT_URL"` // optional for only GitHub
EndpointURL string `yaml:"EndpointURL" env:"ENDPOINT_URL"` // Optional for GitHub, mandatory for Okta
}
type proxyAuthConf struct {
@ -58,7 +59,7 @@ type proxyAuthConf struct {
// Config contains the default values
var Config = Configuration{
ListenAddr: ":8080",
BaseURL: "http://localhost:3000",
BaseURL: "http://localhost:8080",
DisplayURL: "",
DataDir: "data",
Backend: "boltdb",

View File

@ -35,6 +35,9 @@
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"resolutions": {
"react-router": "4.3.1"
},
"browserslist": [
">0.2%",
"not dead",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -131,6 +131,12 @@ export default class BaseComponent extends Component {
<Button style={{ backgroundColor: "#333", color: "white" }} onClick={this.onOAuthClick.bind(this, "github")}>
<Icon name='github' /> Login with GitHub
</Button>
{info.providers.includes("okta") && <div className="ui divider"></div>}
</div>}
{info.providers.includes("okta") && <div>
<Button style={{ color: "#007dc1" }} onClick={this.onOAuthClick.bind(this, "okta")}>
<Image src='/images/okta_logo.png' style={{ width: "16px", height: "16px", marginBottom: ".15em" }} avatar /> Login with Okta
</Button>
</div>}
{info.providers.includes("microsoft") && <div>
<div className="ui divider"></div>