Skip to content

Commit 63d502f

Browse files
trevormduncanmxschmitt
authored andcommitted
Okta Integration - Bring Okta OAuth as an available option (mxschmitt#128)
- Edited package.json for react bug + remix-run/react-router#6630
1 parent ea5f8c8 commit 63d502f

File tree

8 files changed

+109
-6
lines changed

8 files changed

+109
-6
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
- Expirable Links
1616
- URL deletion
1717
- Multiple authorization strategies:
18-
- Local authorization via OAuth 2.0 (Google, GitHub and Microsoft)
18+
- Local authorization via OAuth 2.0 (Google, GitHub, Microsoft, and Okta)
1919
- Proxy authorization for running behind e.g. [Google IAP](https://cloud.google.com/iap/)
2020
- Easy [ShareX](https://github.com/ShareX/ShareX) integration
2121
- Dockerizable

config/example.yaml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
ListenAddr: ':8080' # Consists of 'IP:Port', e.g. ':8080' listens on any IP and on Port 8080
2-
BaseURL: 'http://localhost:3000' # Origin URL, required for the authentication via OAuth callback
2+
BaseURL: 'http://localhost:8080' # Origin URL, required for the authentication via OAuth callback
33
DisplayURL: '' # (OPTIONAL) Display URL, how the apication will present itself in the UI - if not set, defaults to BaseURL
44
Backend: boltdb # Can be 'boltdb' or 'redis'
55
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
1010
AuthBackend: oauth # Can be 'oauth' or 'proxy'
1111
Google: # only relevant when using the oauth authbackend
1212
ClientID: replace me
13-
ClientSecret: replace me
13+
ClientSecret: 'replace me'
1414
GitHub: # only relevant when using the oauth authbackend
1515
ClientID: replace me
16-
ClientSecret: replace me
16+
ClientSecret: 'replace me'
1717
EndpointURL: # (OPTIONAL) URL for custom endpoint (currently only for github); e.g. 'https://github.mydomain.com'
1818
Microsoft: # only relevant when using the oauth authbackend
1919
ClientID: replace me
2020
ClientSecret: 'replace me'
21+
Okta: # only relevant when using the oauth authbackend
22+
ClientID: replace me
23+
ClientSecret: 'replace me'
24+
EndpointURL: # (MANDATORY) Issuer URL from the OAuth API => Authorization Servers in Okta
2125
Proxy: # only relevant when using the proxy authbackend
2226
RequireUserHeader: false # If true, will reject connections that do not have the UserHeader set
2327
UserHeader: "X-Goog-Authenticated-User-ID" # pull the unique user ID from this header

internal/handlers/auth.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ func (h *Handler) initOAuth() {
4141
auth.WithAdapterWrapper(auth.NewMicrosoftAdapter(microsoft.ClientID, microsoft.ClientSecret), h.engine.Group("/api/v1/auth/microsoft"))
4242
h.providers = append(h.providers, "microsoft")
4343
}
44+
okta := util.GetConfig().Okta
45+
if okta.Enabled() {
46+
auth.WithAdapterWrapper(auth.NewOktaAdapter(okta.ClientID, okta.ClientSecret, okta.EndpointURL), h.engine.Group("/api/v1/auth/okta"))
47+
h.providers = append(h.providers, "okta")
48+
}
4449

4550
h.engine.POST("/api/v1/auth/check", h.handleAuthCheck)
4651
}

internal/handlers/auth/okta.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package auth
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/url"
7+
"strings"
8+
9+
"github.com/mxschmitt/golang-url-shortener/internal/util"
10+
"github.com/sirupsen/logrus"
11+
12+
"github.com/pkg/errors"
13+
"golang.org/x/oauth2"
14+
)
15+
16+
type oktaAdapter struct {
17+
config *oauth2.Config
18+
}
19+
20+
// NewOktaAdapter creates an oAuth adapter out of the credentials and the baseURL
21+
func NewOktaAdapter(clientID, clientSecret, endpointURL string) Adapter {
22+
23+
if endpointURL == "" {
24+
logrus.Error("Configure Okta Endpoint")
25+
}
26+
27+
return &oktaAdapter{&oauth2.Config{
28+
ClientID: clientID,
29+
ClientSecret: clientSecret,
30+
RedirectURL: util.GetConfig().BaseURL + "/api/v1/auth/okta/callback",
31+
Scopes: []string{
32+
"profile",
33+
"openid",
34+
"offline_access",
35+
},
36+
Endpoint: oauth2.Endpoint{
37+
AuthURL: endpointURL + "/v1/authorize",
38+
TokenURL: endpointURL + "/v1/token",
39+
},
40+
}}
41+
}
42+
43+
func (a *oktaAdapter) GetRedirectURL(state string) string {
44+
return a.config.AuthCodeURL(state)
45+
}
46+
47+
func (a *oktaAdapter) GetUserData(state, code string) (*user, error) {
48+
49+
logrus.Debugf("Getting User Data with state: %s, and code: %s", state, code)
50+
oAuthToken, err := a.config.Exchange(context.Background(), code)
51+
if err != nil {
52+
return nil, errors.Wrap(err, "could not exchange code")
53+
}
54+
if util.GetConfig().Okta.EndpointURL == "" {
55+
logrus.Error("Okta EndpointURL is Empty")
56+
}
57+
oktaUrl, err := url.Parse(util.GetConfig().Okta.EndpointURL)
58+
if err != nil {
59+
return nil, errors.Wrap(err, "could not parse Okta EndpointURL")
60+
}
61+
oktaBaseURL := strings.Replace(oktaUrl.String(), oktaUrl.RequestURI(), "", 1)
62+
oAuthUserInfoReq, err := a.config.Client(context.Background(), oAuthToken).Get(oktaBaseURL + "/api/v1/users/me")
63+
if err != nil {
64+
return nil, errors.Wrap(err, "could not get user data")
65+
}
66+
defer oAuthUserInfoReq.Body.Close()
67+
var oUser struct {
68+
ID int `json:"sub"`
69+
// Custom URL property for user Avatar can go here
70+
Name string `json:"name"`
71+
}
72+
if err = json.NewDecoder(oAuthUserInfoReq.Body).Decode(&oUser); err != nil {
73+
return nil, errors.Wrap(err, "decoding user info failed")
74+
}
75+
return &user{
76+
ID: string(oUser.ID),
77+
Name: oUser.Name,
78+
Picture: util.GetConfig().BaseURL + "/images/okta_logo.png", // Default Okta Avatar
79+
}, nil
80+
}
81+
82+
func (a *oktaAdapter) GetOAuthProviderName() string {
83+
return "okta"
84+
}

internal/util/config.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Configuration struct {
2828
Google oAuthConf `yaml:"Google" env:"GOOGLE"`
2929
GitHub oAuthConf `yaml:"GitHub" env:"GITHUB"`
3030
Microsoft oAuthConf `yaml:"Microsoft" env:"MICROSOFT"`
31+
Okta oAuthConf `yaml:"Okta" env:"OKTA"`
3132
Proxy proxyAuthConf `yaml:"Proxy" env:"PROXY"`
3233
Redis redisConf `yaml:"Redis" env:"REDIS"`
3334
}
@@ -46,7 +47,7 @@ type redisConf struct {
4647
type oAuthConf struct {
4748
ClientID string `yaml:"ClientID" env:"CLIENT_ID"`
4849
ClientSecret string `yaml:"ClientSecret" env:"CLIENT_SECRET"`
49-
EndpointURL string `yaml:"EndPointURL" env:"ENDPOINT_URL"` // optional for only GitHub
50+
EndpointURL string `yaml:"EndpointURL" env:"ENDPOINT_URL"` // Optional for GitHub, mandatory for Okta
5051
}
5152

5253
type proxyAuthConf struct {
@@ -58,7 +59,7 @@ type proxyAuthConf struct {
5859
// Config contains the default values
5960
var Config = Configuration{
6061
ListenAddr: ":8080",
61-
BaseURL: "http://localhost:3000",
62+
BaseURL: "http://localhost:8080",
6263
DisplayURL: "",
6364
DataDir: "data",
6465
Backend: "boltdb",

web/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
"test": "react-scripts test --env=jsdom",
3636
"eject": "react-scripts eject"
3737
},
38+
"resolutions": {
39+
"react-router": "4.3.1"
40+
},
3841
"browserslist": [
3942
">0.2%",
4043
"not dead",

web/public/images/okta_logo.png

1.28 KB
Loading

web/src/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ export default class BaseComponent extends Component {
131131
<Button style={{ backgroundColor: "#333", color: "white" }} onClick={this.onOAuthClick.bind(this, "github")}>
132132
<Icon name='github' /> Login with GitHub
133133
</Button>
134+
{info.providers.includes("okta") && <div className="ui divider"></div>}
135+
</div>}
136+
{info.providers.includes("okta") && <div>
137+
<Button style={{ color: "#007dc1" }} onClick={this.onOAuthClick.bind(this, "okta")}>
138+
<Image src='/images/okta_logo.png' style={{ width: "16px", height: "16px", marginBottom: ".15em" }} avatar /> Login with Okta
139+
</Button>
134140
</div>}
135141
{info.providers.includes("microsoft") && <div>
136142
<div className="ui divider"></div>

0 commit comments

Comments
 (0)