Skip to content

Commit 049242d

Browse files
committed
feat(api): expose post_logout_redirect_uris over gRPC
Added to Client, ClientInfo and UpdateClientReq, alongside the back-channel logout URI. Without it the API could register where dex pushes a logout token but not where the browser is allowed to land afterwards, so a client created through the API could never complete an RP-initiated logout. Unlike backchannel_logout_uri this needs no explicit presence: a repeated field already distinguishes an absent list from an empty one, so sending an empty list clears the URIs. The example app's admin page grows the field on the create and edit forms and on the client detail view. Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
1 parent a1d7803 commit 049242d

8 files changed

Lines changed: 766 additions & 685 deletions

File tree

api/v2/api.pb.go

Lines changed: 637 additions & 600 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v2/api.proto

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ message Client {
2020
// ends, per OIDC Back-Channel Logout 1.0. Empty means the client is not
2121
// notified.
2222
string backchannel_logout_uri = 10;
23+
// Where the browser may be sent after an RP-initiated logout. A
24+
// post_logout_redirect_uri that is not listed here is refused.
25+
repeated string post_logout_redirect_uris = 11;
2326
}
2427

2528
// ClientInfo represents an OAuth2 client without sensitive information.
@@ -33,6 +36,7 @@ message ClientInfo {
3336
repeated string allowed_connectors = 7;
3437
repeated string sso_shared_with = 8;
3538
string backchannel_logout_uri = 9;
39+
repeated string post_logout_redirect_uris = 10;
3640
}
3741

3842
// GetClientReq is a request to retrieve client details.
@@ -81,6 +85,7 @@ message UpdateClientReq {
8185
// a client could be given a back-channel endpoint but never relieved of one,
8286
// leaving dex posting logout tokens at something that no longer exists.
8387
optional string backchannel_logout_uri = 8;
88+
repeated string post_logout_redirect_uris = 9;
8489
}
8590

8691
// UpdateClientResp returns the response from updating a client.

examples/example-app/server/admin.go

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,16 @@ func (s *Server) handleAdmin(w http.ResponseWriter, r *http.Request) {
156156
if resp, err := s.admin.api.ListClients(ctx, &api.ListClientReq{}); err == nil {
157157
for _, c := range resp.Clients {
158158
data.Clients = append(data.Clients, AdminClient{
159-
ID: c.Id,
160-
Name: c.Name,
161-
RedirectURIs: c.RedirectUris,
162-
TrustedPeers: c.TrustedPeers,
163-
Public: c.Public,
164-
LogoURL: c.LogoUrl,
165-
AllowedConnectors: c.AllowedConnectors,
166-
SSOSharedWith: c.SsoSharedWith,
167-
BackchannelLogoutURI: c.BackchannelLogoutUri,
159+
ID: c.Id,
160+
Name: c.Name,
161+
RedirectURIs: c.RedirectUris,
162+
TrustedPeers: c.TrustedPeers,
163+
Public: c.Public,
164+
LogoURL: c.LogoUrl,
165+
AllowedConnectors: c.AllowedConnectors,
166+
SSOSharedWith: c.SsoSharedWith,
167+
BackchannelLogoutURI: c.BackchannelLogoutUri,
168+
PostLogoutRedirectURIs: c.PostLogoutRedirectUris,
168169
})
169170
}
170171
} else {
@@ -268,15 +269,16 @@ func (s *Server) handleAdmin(w http.ResponseWriter, r *http.Request) {
268269
if resp, err := s.admin.api.GetClient(ctx, &api.GetClientReq{Id: id}); err == nil && resp.Client != nil {
269270
c := resp.Client
270271
data.EditClient = &AdminClient{
271-
ID: c.Id,
272-
Name: c.Name,
273-
RedirectURIs: c.RedirectUris,
274-
TrustedPeers: c.TrustedPeers,
275-
Public: c.Public,
276-
LogoURL: c.LogoUrl,
277-
AllowedConnectors: c.AllowedConnectors,
278-
SSOSharedWith: c.SsoSharedWith,
279-
BackchannelLogoutURI: c.BackchannelLogoutUri,
272+
ID: c.Id,
273+
Name: c.Name,
274+
RedirectURIs: c.RedirectUris,
275+
TrustedPeers: c.TrustedPeers,
276+
Public: c.Public,
277+
LogoURL: c.LogoUrl,
278+
AllowedConnectors: c.AllowedConnectors,
279+
SSOSharedWith: c.SsoSharedWith,
280+
BackchannelLogoutURI: c.BackchannelLogoutUri,
281+
PostLogoutRedirectURIs: c.PostLogoutRedirectUris,
280282
}
281283
} else if err != nil {
282284
fail(err)
@@ -316,16 +318,17 @@ func (s *Server) handleAdminCreateClient(w http.ResponseWriter, r *http.Request)
316318

317319
req := &api.CreateClientReq{
318320
Client: &api.Client{
319-
Id: r.FormValue("id"),
320-
Name: r.FormValue("name"),
321-
Secret: r.FormValue("secret"),
322-
LogoUrl: r.FormValue("logo_url"),
323-
RedirectUris: r.Form["redirect_uris"],
324-
TrustedPeers: r.Form["trusted_peers"],
325-
AllowedConnectors: r.Form["allowed_connectors"],
326-
SsoSharedWith: r.Form["sso_shared_with"],
327-
BackchannelLogoutUri: r.FormValue("backchannel_logout_uri"),
328-
Public: r.FormValue("public") != "",
321+
Id: r.FormValue("id"),
322+
Name: r.FormValue("name"),
323+
Secret: r.FormValue("secret"),
324+
LogoUrl: r.FormValue("logo_url"),
325+
RedirectUris: r.Form["redirect_uris"],
326+
TrustedPeers: r.Form["trusted_peers"],
327+
AllowedConnectors: r.Form["allowed_connectors"],
328+
SsoSharedWith: r.Form["sso_shared_with"],
329+
BackchannelLogoutUri: r.FormValue("backchannel_logout_uri"),
330+
PostLogoutRedirectUris: r.Form["post_logout_redirect_uris"],
331+
Public: r.FormValue("public") != "",
329332
},
330333
}
331334

@@ -502,14 +505,15 @@ func (s *Server) handleAdminUpdateClient(w http.ResponseWriter, r *http.Request)
502505
backchannelLogoutURI := r.FormValue("backchannel_logout_uri")
503506

504507
resp, err := s.admin.api.UpdateClient(ctx, &api.UpdateClientReq{
505-
Id: id,
506-
Name: r.FormValue("name"),
507-
LogoUrl: r.FormValue("logo_url"),
508-
RedirectUris: r.Form["redirect_uris"],
509-
TrustedPeers: r.Form["trusted_peers"],
510-
AllowedConnectors: r.Form["allowed_connectors"],
511-
SsoSharedWith: r.Form["sso_shared_with"],
512-
BackchannelLogoutUri: &backchannelLogoutURI,
508+
Id: id,
509+
Name: r.FormValue("name"),
510+
LogoUrl: r.FormValue("logo_url"),
511+
RedirectUris: r.Form["redirect_uris"],
512+
TrustedPeers: r.Form["trusted_peers"],
513+
AllowedConnectors: r.Form["allowed_connectors"],
514+
SsoSharedWith: r.Form["sso_shared_with"],
515+
BackchannelLogoutUri: &backchannelLogoutURI,
516+
PostLogoutRedirectUris: r.Form["post_logout_redirect_uris"],
513517
})
514518
switch {
515519
case err != nil:

examples/example-app/server/admindetail.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,17 @@ func (s *Server) handleAdminClientDetail(w http.ResponseWriter, r *http.Request)
3939
default:
4040
c := resp.Client
4141
data.Client = &AdminClient{
42-
ID: c.Id,
43-
Name: c.Name,
44-
Secret: c.Secret,
45-
RedirectURIs: c.RedirectUris,
46-
TrustedPeers: c.TrustedPeers,
47-
Public: c.Public,
48-
LogoURL: c.LogoUrl,
49-
AllowedConnectors: c.AllowedConnectors,
50-
SSOSharedWith: c.SsoSharedWith,
51-
BackchannelLogoutURI: c.BackchannelLogoutUri,
42+
ID: c.Id,
43+
Name: c.Name,
44+
Secret: c.Secret,
45+
RedirectURIs: c.RedirectUris,
46+
TrustedPeers: c.TrustedPeers,
47+
Public: c.Public,
48+
LogoURL: c.LogoUrl,
49+
AllowedConnectors: c.AllowedConnectors,
50+
SSOSharedWith: c.SsoSharedWith,
51+
BackchannelLogoutURI: c.BackchannelLogoutUri,
52+
PostLogoutRedirectURIs: c.PostLogoutRedirectUris,
5253
}
5354
}
5455

examples/example-app/server/render.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,17 @@ type AdminRefreshToken struct {
223223
// AdminClient is one OAuth2 client as the API reports it — every field it has,
224224
// since a list that shows three of nine invites you to guess the rest.
225225
type AdminClient struct {
226-
ID string
227-
Name string
228-
Secret string
229-
RedirectURIs []string
230-
TrustedPeers []string
231-
Public bool
232-
LogoURL string
233-
AllowedConnectors []string
234-
SSOSharedWith []string
235-
BackchannelLogoutURI string
226+
ID string
227+
Name string
228+
Secret string
229+
RedirectURIs []string
230+
TrustedPeers []string
231+
Public bool
232+
LogoURL string
233+
AllowedConnectors []string
234+
SSOSharedWith []string
235+
BackchannelLogoutURI string
236+
PostLogoutRedirectURIs []string
236237
}
237238

238239
// AdminPassword is one local password entry as the API reports it.

examples/example-app/server/templates/admin.html

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@
147147
<small class="hint">Which clients may reuse this one's session. * for all.</small>
148148
</div>
149149
</div>
150+
<div class="form-row form-row--wide">
151+
<label for="post_logout_redirect_uris_input">Post-logout redirect URIs</label>
152+
<div class="form-control">
153+
<div class="chips" data-name="post_logout_redirect_uris">
154+
</div>
155+
<div class="inline-input">
156+
<input type="text" id="post_logout_redirect_uris_input" class="chip-input" autocomplete="off">
157+
<button type="button" class="button button-small chip-add">Add</button>
158+
</div>
159+
<small class="hint">Where the browser may be sent after logging out. Anything not listed is refused.</small>
160+
</div>
161+
</div>
150162
<div class="form-row form-row--wide">
151163
<label for="c_backchannel_logout_uri">Back-channel logout URI</label>
152164
<div class="form-control">
@@ -235,6 +247,19 @@
235247
<small class="hint">Which clients may reuse this one's session. * for all.</small>
236248
</div>
237249
</div>
250+
<div class="form-row form-row--wide">
251+
<label for="post_logout_redirect_uris_input">Post-logout redirect URIs</label>
252+
<div class="form-control">
253+
<div class="chips" data-name="post_logout_redirect_uris">
254+
{{range .PostLogoutRedirectURIs}}<span class="chip"><span>{{.}}</span><input type="hidden" name="post_logout_redirect_uris" value="{{.}}"><button type="button" class="chip-remove">&times;</button></span>{{end}}
255+
</div>
256+
<div class="inline-input">
257+
<input type="text" id="post_logout_redirect_uris_input" class="chip-input" autocomplete="off">
258+
<button type="button" class="button button-small chip-add">Add</button>
259+
</div>
260+
<small class="hint">Where the browser may be sent after logging out. Anything not listed is refused.</small>
261+
</div>
262+
</div>
238263
<div class="form-row form-row--wide">
239264
<label for="e_backchannel_logout_uri">Back-channel logout URI</label>
240265
<div class="form-control">

examples/example-app/server/templates/detail.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
<div class="v mono">{{range .AllowedConnectors}}{{.}}<br>{{else}}<span class="hint">all</span>{{end}}</div>
2828
<div class="k">SSO shared with</div>
2929
<div class="v mono">{{range .SSOSharedWith}}{{.}}<br>{{else}}<span class="hint">the sessions default</span>{{end}}</div>
30+
<div class="k">Post-logout redirect URIs</div>
31+
<div class="v mono">{{range .PostLogoutRedirectURIs}}{{.}}<br>{{else}}<span class="hint">none — logout cannot redirect back</span>{{end}}</div>
3032
<div class="k">Back-channel logout URI</div>
3133
<div class="v mono">{{if .BackchannelLogoutURI}}{{.BackchannelLogoutURI}}{{else}}<span class="hint">not set — this client is not notified when a session ends</span>{{end}}</div>
3234
</div>

server/apiserver/clients.go

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@ func (d dexAPI) GetClient(ctx context.Context, req *api.GetClientReq) (*api.GetC
1717

1818
return &api.GetClientResp{
1919
Client: &api.Client{
20-
Id: c.ID,
21-
Name: c.Name,
22-
Secret: c.Secret,
23-
RedirectUris: c.RedirectURIs,
24-
TrustedPeers: c.TrustedPeers,
25-
Public: c.Public,
26-
LogoUrl: c.LogoURL,
27-
AllowedConnectors: c.AllowedConnectors,
28-
SsoSharedWith: c.SSOSharedWith,
29-
BackchannelLogoutUri: c.BackchannelLogoutURI,
20+
Id: c.ID,
21+
Name: c.Name,
22+
Secret: c.Secret,
23+
RedirectUris: c.RedirectURIs,
24+
TrustedPeers: c.TrustedPeers,
25+
Public: c.Public,
26+
LogoUrl: c.LogoURL,
27+
AllowedConnectors: c.AllowedConnectors,
28+
SsoSharedWith: c.SSOSharedWith,
29+
BackchannelLogoutUri: c.BackchannelLogoutURI,
30+
PostLogoutRedirectUris: c.PostLogoutRedirectURIs,
3031
},
3132
}, nil
3233
}
@@ -44,16 +45,17 @@ func (d dexAPI) CreateClient(ctx context.Context, req *api.CreateClientReq) (*ap
4445
}
4546

4647
c := storage.Client{
47-
ID: req.Client.Id,
48-
Secret: req.Client.Secret,
49-
RedirectURIs: req.Client.RedirectUris,
50-
TrustedPeers: req.Client.TrustedPeers,
51-
Public: req.Client.Public,
52-
Name: req.Client.Name,
53-
LogoURL: req.Client.LogoUrl,
54-
AllowedConnectors: req.Client.AllowedConnectors,
55-
SSOSharedWith: req.Client.SsoSharedWith,
56-
BackchannelLogoutURI: req.Client.BackchannelLogoutUri,
48+
ID: req.Client.Id,
49+
Secret: req.Client.Secret,
50+
RedirectURIs: req.Client.RedirectUris,
51+
TrustedPeers: req.Client.TrustedPeers,
52+
Public: req.Client.Public,
53+
Name: req.Client.Name,
54+
LogoURL: req.Client.LogoUrl,
55+
AllowedConnectors: req.Client.AllowedConnectors,
56+
SSOSharedWith: req.Client.SsoSharedWith,
57+
BackchannelLogoutURI: req.Client.BackchannelLogoutUri,
58+
PostLogoutRedirectURIs: req.Client.PostLogoutRedirectUris,
5759
}
5860
if err := d.s.CreateClient(ctx, c); err != nil {
5961
if err == storage.ErrAlreadyExists {
@@ -97,6 +99,9 @@ func (d dexAPI) UpdateClient(ctx context.Context, req *api.UpdateClientReq) (*ap
9799
if req.BackchannelLogoutUri != nil {
98100
old.BackchannelLogoutURI = req.GetBackchannelLogoutUri()
99101
}
102+
if req.PostLogoutRedirectUris != nil {
103+
old.PostLogoutRedirectURIs = req.PostLogoutRedirectUris
104+
}
100105
return old, nil
101106
})
102107
if err != nil {
@@ -131,15 +136,16 @@ func (d dexAPI) ListClients(ctx context.Context, req *api.ListClientReq) (*api.L
131136
clients := make([]*api.ClientInfo, 0, len(clientList))
132137
for _, client := range clientList {
133138
c := api.ClientInfo{
134-
Id: client.ID,
135-
Name: client.Name,
136-
RedirectUris: client.RedirectURIs,
137-
TrustedPeers: client.TrustedPeers,
138-
Public: client.Public,
139-
LogoUrl: client.LogoURL,
140-
AllowedConnectors: client.AllowedConnectors,
141-
SsoSharedWith: client.SSOSharedWith,
142-
BackchannelLogoutUri: client.BackchannelLogoutURI,
139+
Id: client.ID,
140+
Name: client.Name,
141+
RedirectUris: client.RedirectURIs,
142+
TrustedPeers: client.TrustedPeers,
143+
Public: client.Public,
144+
LogoUrl: client.LogoURL,
145+
AllowedConnectors: client.AllowedConnectors,
146+
SsoSharedWith: client.SSOSharedWith,
147+
BackchannelLogoutUri: client.BackchannelLogoutURI,
148+
PostLogoutRedirectUris: client.PostLogoutRedirectURIs,
143149
}
144150
clients = append(clients, &c)
145151
}

0 commit comments

Comments
 (0)