Skip to content

Commit c548516

Browse files
committed
add oidc implicit flow support
1 parent bdf6de8 commit c548516

File tree

15 files changed

+305
-50
lines changed

15 files changed

+305
-50
lines changed

server/config/development.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ batchActionsDisabled: false
2020
hideWorkflowQueryErrors: false
2121
auth:
2222
enabled: false
23-
providers:
23+
providers: # only the first is used. second is provided for reference
2424
- label: Auth0 oidc # for internal use; in future may expose as button text
2525
type: oidc # for futureproofing; only oidc is supported today
2626
providerUrl: https://myorg.us.auth0.com/
@@ -36,6 +36,17 @@ auth:
3636
audience: myorg-dev
3737
organization: org_xxxxxxxxxxxx
3838
invitation:
39+
- label: oidc implicit flow
40+
type: oidc
41+
flow: implicit
42+
# TODO: support optional issuer validation
43+
authorizationUrl: https://accounts.google.com/o/oauth2/v2/auth # discovery isn't supported for implicit flow. the endpoint must be provided directly
44+
clientId: xxxxxxxxxxxxxxxxxxxx
45+
scopes:
46+
- openid
47+
- profile
48+
- email
49+
callbackUrl: http://localhost:8080
3950
tls:
4051
caFile:
4152
certFile:

server/docker/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ docker run \
1515
-e TEMPORAL_ADDRESS=127.0.0.1:7233 \
1616
-e TEMPORAL_UI_PORT=8080 \
1717
-e TEMPORAL_AUTH_ENABLED=true \
18+
-e TEMPORAL_AUTH_FLOW_TYPE=authorization-code \
1819
-e TEMPORAL_AUTH_PROVIDER_URL=https://accounts.google.com \
1920
-e TEMPORAL_AUTH_CLIENT_ID=xxxxx-xxxx.apps.googleusercontent.com \
2021
-e TEMPORAL_AUTH_CLIENT_SECRET=xxxxxxxxxxxxxxx \

server/docker/config-template.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ auth:
4040
providers:
4141
- label: {{ default .Env.TEMPORAL_AUTH_LABEL "sso" }}
4242
type: {{ default .Env.TEMPORAL_AUTH_TYPE "oidc" }}
43+
flow: {{ default .Env.TEMPORAL_AUTH_FLOW_TYPE "authorization-code" }}
4344
providerUrl: {{ .Env.TEMPORAL_AUTH_PROVIDER_URL }}
4445
issuerUrl: {{ default .Env.TEMPORAL_AUTH_ISSUER_URL "" }}
46+
authorizationUrl:: {{ default .Env.TEMPORAL_AUTH_AUTHORIZATION_URL "" }}
4547
clientId: {{ .Env.TEMPORAL_AUTH_CLIENT_ID }}
4648
clientSecret: {{ .Env.TEMPORAL_AUTH_CLIENT_SECRET }}
4749
callbackUrl: {{ .Env.TEMPORAL_AUTH_CALLBACK_URL }}

server/server/api/handler.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,15 @@ import (
4545
)
4646

4747
type Auth struct {
48-
Enabled bool
49-
Options []string
48+
Enabled bool
49+
Flow string
50+
ProviderURL string
51+
IssuerURL string
52+
AuthorizationURL string
53+
ClientID string
54+
CallbackURL string
55+
Scopes []string
56+
Options []string
5057
}
5158

5259
type CodecResponse struct {
@@ -110,17 +117,25 @@ func GetSettings(cfgProvider *config.ConfigProviderWithRefresh) func(echo.Contex
110117
}
111118

112119
var options []string
120+
var authProviderCfg config.AuthProvider
113121
if len(cfg.Auth.Providers) != 0 {
114-
authProviderCfg := cfg.Auth.Providers[0].Options
115-
for k := range authProviderCfg {
122+
authProviderCfg = cfg.Auth.Providers[0]
123+
for k := range authProviderCfg.Options {
116124
options = append(options, k)
117125
}
118126
}
119127

120128
settings := &SettingsResponse{
121129
Auth: &Auth{
122-
Enabled: cfg.Auth.Enabled,
123-
Options: options,
130+
Enabled: cfg.Auth.Enabled,
131+
Flow: authProviderCfg.Flow,
132+
ProviderURL: authProviderCfg.ProviderURL,
133+
IssuerURL: authProviderCfg.IssuerURL,
134+
AuthorizationURL: authProviderCfg.AuthorizationURL,
135+
ClientID: authProviderCfg.ClientID,
136+
CallbackURL: authProviderCfg.CallbackURL,
137+
Scopes: authProviderCfg.Scopes,
138+
Options: options,
124139
},
125140
BannerText: cfg.BannerText,
126141
DefaultNamespace: cfg.DefaultNamespace,

server/server/config/auth.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,27 @@ func (c *AuthProvider) validate() error {
5252
return nil
5353
}
5454

55-
if c.ProviderURL == "" {
56-
return errors.New("auth provider url is not")
55+
switch flowType := c.Flow; flowType {
56+
case "authorization-code":
57+
if c.ProviderURL == "" {
58+
return errors.New("auth provider url is not set")
59+
}
60+
if c.AuthorizationURL == "" {
61+
return errors.New("auth endpoint url is not used in auth code flow")
62+
}
63+
case "implicit":
64+
if c.ProviderURL != "" {
65+
return errors.New("auth provider url is not used in implicit flow")
66+
}
67+
// TODO: support optional issuer validation
68+
if c.AuthorizationURL == "" {
69+
return errors.New("auth issuer url is not set")
70+
}
71+
if c.ClientSecret != "" {
72+
return errors.New("no secrets in implicit flow")
73+
}
74+
default:
75+
return errors.New("auth oidc flow is not valid")
5776
}
5877

5978
if c.ClientID == "" {

server/server/config/config.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,16 @@ type (
9797
Label string `yaml:"label"`
9898
// Type of the auth provider. Only OIDC is supported today
9999
Type string `yaml:"type"`
100-
// OIDC .well-known/openid-configuration URL, ex. https://accounts.google.com/
100+
// OIDC login flow type. The "authorization-code" and "implicit" flows are supported
101+
Flow string `yaml:"flow"`
102+
// OIDC .well-known/openid-configuration URL, e.g. https://accounts.google.com/. Discovery unsupported in implicit flow.
101103
ProviderURL string `yaml:"providerUrl"`
102-
// Optional. Needed only when differs from the auth provider URL
103-
IssuerURL string `yaml:"issuerUrl"`
104-
ClientID string `yaml:"clientId"`
105-
ClientSecret string `yaml:"clientSecret"`
104+
// Optional. Needed only when differs from the auth provider URL. In implicit flow, enables token issuer validation.
105+
IssuerURL string `yaml:"issuerUrl"`
106+
// Required for implicit flow. OIDC authorization endpoint URL, e.g. https://accounts.google.com/o/oauth2/v2/auth
107+
AuthorizationURL string `yaml:"authorizationUrl"`
108+
ClientID string `yaml:"clientId"`
109+
ClientSecret string `yaml:"clientSecret"`
106110
// Scopes for auth. Typically [openid, profile, email]
107111
Scopes []string `yaml:"scopes"`
108112
// URL for the callback, e.g. https://localhost:8080/sso/callback

server/server/route/auth.go

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,26 +59,37 @@ func SetAuthRoutes(e *echo.Echo, cfgProvider *config.ConfigProviderWithRefresh)
5959

6060
providerCfg := serverCfg.Auth.Providers[0] // only single provider is currently supported
6161

62-
if len(providerCfg.IssuerURL) > 0 {
63-
ctx = oidc.InsecureIssuerURLContext(ctx, providerCfg.IssuerURL)
64-
}
65-
provider, err := oidc.NewProvider(ctx, providerCfg.ProviderURL)
66-
if err != nil {
67-
log.Fatal(err)
68-
}
62+
api := e.Group("/auth")
63+
switch providerCfg.Flow {
64+
case "authorization-code":
65+
if len(providerCfg.IssuerURL) > 0 {
66+
ctx = oidc.InsecureIssuerURLContext(ctx, providerCfg.IssuerURL)
67+
}
6968

70-
oauthCfg := oauth2.Config{
71-
ClientID: providerCfg.ClientID,
72-
ClientSecret: providerCfg.ClientSecret,
73-
Endpoint: provider.Endpoint(),
74-
RedirectURL: providerCfg.CallbackURL,
75-
Scopes: providerCfg.Scopes,
76-
}
69+
if len(providerCfg.AuthorizationURL) > 0 {
70+
log.Fatal(`authorization url should not be set for auth code flow`)
71+
}
7772

78-
api := e.Group("/auth")
79-
api.GET("/sso", authenticate(&oauthCfg, providerCfg.Options))
80-
api.GET("/sso/callback", authenticateCb(ctx, &oauthCfg, provider))
81-
api.GET("/sso_callback", authenticateCb(ctx, &oauthCfg, provider)) // compatibility with UI v1
73+
provider, err := oidc.NewProvider(ctx, providerCfg.ProviderURL)
74+
if err != nil {
75+
log.Fatal(err)
76+
}
77+
78+
oauthCfg := oauth2.Config{
79+
ClientID: providerCfg.ClientID,
80+
ClientSecret: providerCfg.ClientSecret,
81+
Endpoint: provider.Endpoint(),
82+
RedirectURL: providerCfg.CallbackURL,
83+
Scopes: providerCfg.Scopes,
84+
}
85+
86+
api.GET("/sso", authenticate(&oauthCfg, providerCfg.Options))
87+
api.GET("/sso/callback", authenticateCb(ctx, &oauthCfg, provider))
88+
api.GET("/sso_callback", authenticateCb(ctx, &oauthCfg, provider)) // compatibility with UI v1
89+
case "implicit":
90+
// The implicit flow is principally designed for single-page applications.
91+
// Fully delegated to the client.
92+
}
8293
}
8394

8495
func authenticate(config *oauth2.Config, options map[string]interface{}) func(echo.Context) error {

src/lib/services/settings-service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ export const fetchSettings = async (request = fetch): Promise<Settings> => {
2020
const settingsInformation = {
2121
auth: {
2222
enabled: !!settingsResponse?.Auth?.Enabled,
23+
flow: settingsResponse?.Auth?.Flow,
24+
providerUrl: settingsResponse?.Auth?.ProviderURL,
25+
issuerUrl: settingsResponse?.Auth?.IssuerURL,
26+
authorizationUrl: settingsResponse?.Auth?.AuthorizationURL,
27+
clientId: settingsResponse?.Auth?.ClientID,
28+
callbackUrl: settingsResponse?.Auth?.CallbackURL,
29+
scopes: settingsResponse?.Auth?.Scopes,
2330
options: settingsResponse?.Auth?.Options,
2431
},
2532
bannerText: settingsResponse?.BannerText,

src/lib/stores/auth-user.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,26 @@ import { get } from 'svelte/store';
22

33
import { persistStore } from '$lib/stores/persist-store';
44
import type { User } from '$lib/types/global';
5+
import { OIDCFlow } from '$lib/types/global';
56

67
export const authUser = persistStore<User>('AuthUser', {});
78

89
export const getAuthUser = (): User => get(authUser);
910

10-
export const setAuthUser = (user: User) => {
11+
export const setAuthUser = (user: User, oidcFlow: OIDCFlow) => {
1112
const { accessToken, idToken, name, email, picture } = user;
1213

13-
if (!accessToken) {
14-
throw new Error('No access token');
14+
switch (oidcFlow) {
15+
case OIDCFlow.AuthorizationCode:
16+
if (!accessToken) {
17+
throw new Error('No access token');
18+
}
19+
break;
20+
case OIDCFlow.Implicit:
21+
if (!idToken) {
22+
throw new Error('No id token');
23+
}
24+
break;
1525
}
1626

1727
authUser.set({

src/lib/types/global.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,21 @@ export interface NetworkError {
6969
message?: string;
7070
}
7171

72+
export enum OIDCFlow {
73+
AuthorizationCode = 'authorization-code',
74+
Implicit = 'implicit',
75+
}
76+
7277
export type Settings = {
7378
auth: {
7479
enabled: boolean;
80+
flow: OIDCFlow;
81+
providerUrl: string;
82+
issuerUrl: string;
83+
authorizationUrl: string;
84+
clientId: string;
85+
callbackUrl: string;
86+
scopes: string[];
7587
options: string[];
7688
};
7789
bannerText: string;

0 commit comments

Comments
 (0)