Skip to content

Commit dbc9b1e

Browse files
committed
feat: support for prompt=login in oidc flow
1 parent 6ccc894 commit dbc9b1e

6 files changed

Lines changed: 30 additions & 10 deletions

File tree

frontend/src/lib/hooks/screen-params.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type ScreenParams = {
66
oidc_ticket?: string;
77
oidc_scope?: string;
88
oidc_name?: string;
9+
oidc_login?: boolean;
910
};
1011

1112
const zodScreenParams = z.object({
@@ -14,6 +15,7 @@ const zodScreenParams = z.object({
1415
oidc_ticket: z.string().optional(),
1516
oidc_scope: z.string().optional(),
1617
oidc_name: z.string().optional(),
18+
oidc_login: z.stringbool().optional(),
1719
});
1820

1921
export function useScreenParams(params: URLSearchParams): ScreenParams {

frontend/src/pages/authorize-page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export const AuthorizePage = () => {
119119
);
120120
}
121121

122-
if (!auth.authenticated) {
122+
if (!auth.authenticated || screenParams.oidc_login) {
123123
return <Navigate to={`/login${compiledParams}`} replace />;
124124
}
125125

frontend/src/pages/login-page.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ export const LoginPage = () => {
6363

6464
const searchParams = new URLSearchParams(search);
6565
const screenParams = useScreenParams(searchParams);
66-
const compiledParams = recompileScreenParams(screenParams);
66+
const compiledParams = recompileScreenParams({
67+
...screenParams,
68+
oidc_login: false,
69+
});
6770
const loginForUrl = useLoginFor({
6871
login_for: screenParams.login_for,
6972
compiledParams,
@@ -196,7 +199,7 @@ export const LoginPage = () => {
196199
};
197200
}, [redirectTimer, redirectButtonTimer]);
198201

199-
if (auth.authenticated) {
202+
if (auth.authenticated && !screenParams.oidc_login) {
200203
return <Navigate to={loginForUrl} replace />;
201204
}
202205

internal/controller/oidc_controller.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type AuthorizeScreenParams struct {
7373
OIDCTicket string `url:"oidc_ticket"`
7474
OIDCScope string `url:"oidc_scope"`
7575
OIDCName string `url:"oidc_name"`
76+
OIDCLogin bool `url:"oidc_login"`
7677
}
7778

7879
type AuthorizeCompleteRequest struct {
@@ -169,12 +170,18 @@ func (controller *OIDCController) authorize(c *gin.Context) {
169170

170171
ticket := controller.oidc.CreateAuthorizeRequestTicket(*req)
171172

172-
queries, err := query.Values(AuthorizeScreenParams{
173+
values := AuthorizeScreenParams{
173174
LoginFor: FrontendLoginForOIDC,
174175
OIDCTicket: ticket,
175176
OIDCScope: req.Scope,
176177
OIDCName: client.Name,
177-
})
178+
}
179+
180+
if req.Prompt == "login" {
181+
values.OIDCLogin = true
182+
}
183+
184+
queries, err := query.Values(values)
178185

179186
if err != nil {
180187
controller.authorizeError(c, authorizeErrorParams{
@@ -425,7 +432,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
425432
return
426433
}
427434

428-
tokenRes, err := controller.oidc.GenerateAccessToken(c, client, *entry)
435+
tokenRes, err := controller.oidc.GenerateAccessToken(c, client, *entry, entry.AuthTime)
429436

430437
if err != nil {
431438
controller.log.App.Error().Err(err).Msg("Failed to generate access token")

internal/model/context.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
type UserContext struct {
2626
Authenticated bool
2727
Provider ProviderType
28+
AuthTime int64
2829
Local *LocalContext
2930
OAuth *OAuthContext
3031
LDAP *LDAPContext
@@ -110,6 +111,7 @@ func (c *UserContext) NewFromGin(ginctx *gin.Context) (*UserContext, error) {
110111
func (c *UserContext) NewFromSession(session *repository.Session) (*UserContext, error) {
111112
*c = UserContext{
112113
Authenticated: !session.TotpPending,
114+
AuthTime: session.CreatedAt,
113115
}
114116

115117
switch session.Provider {

internal/service/oidc_service.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type ClaimSet struct {
5454
Sub string `json:"sub"`
5555
Iat int64 `json:"iat"`
5656
Exp int64 `json:"exp"`
57+
AuthTime int64 `json:"auth_time,omitempty"`
5758
Name string `json:"name,omitempty"`
5859
GivenName string `json:"given_name,omitempty"`
5960
FamilyName string `json:"family_name,omitempty"`
@@ -117,6 +118,7 @@ type AuthorizeRequest struct {
117118
Nonce string `form:"nonce" json:"nonce" url:"nonce"`
118119
CodeChallenge string `form:"code_challenge" json:"code_challenge" url:"code_challenge"`
119120
CodeChallengeMethod string `form:"code_challenge_method" json:"code_challenge_method" url:"code_challenge_method"`
121+
Prompt string `form:"prompt" json:"prompt" url:"prompt"`
120122
}
121123

122124
type AuthorizeCodeEntry struct {
@@ -127,6 +129,7 @@ type AuthorizeCodeEntry struct {
127129
Nonce string
128130
CodeChallenge string
129131
Userinfo UserinfoResponse
132+
AuthTime int64
130133
}
131134

132135
type UsedCodeEntry struct {
@@ -423,6 +426,7 @@ func (service *OIDCService) CreateCode(req AuthorizeRequest, userContext model.U
423426
ClientID: req.ClientID,
424427
Nonce: req.Nonce,
425428
Userinfo: service.userinfoFromContext(userContext, sub),
429+
AuthTime: userContext.AuthTime,
426430
}
427431

428432
if req.CodeChallenge != "" {
@@ -512,7 +516,7 @@ func (service *OIDCService) GetCodeEntry(codeHash string, clientId string) (*Aut
512516
return &entry, true
513517
}
514518

515-
func (service *OIDCService) generateIDToken(client model.OIDCClientConfig, user UserinfoResponse, scope string, nonce string) (string, error) {
519+
func (service *OIDCService) generateIDToken(client model.OIDCClientConfig, user UserinfoResponse, scope string, nonce string, auth_time int64) (string, error) {
516520
createdAt := time.Now().Unix()
517521
expiresAt := time.Now().Add(time.Duration(service.config.Auth.SessionExpiry) * time.Second).Unix()
518522

@@ -549,6 +553,7 @@ func (service *OIDCService) generateIDToken(client model.OIDCClientConfig, user
549553
Sub: user.Sub,
550554
Iat: createdAt,
551555
Exp: expiresAt,
556+
AuthTime: auth_time,
552557
Name: userInfo.Name,
553558
Email: userInfo.Email,
554559
EmailVerified: userInfo.EmailVerified,
@@ -578,8 +583,8 @@ func (service *OIDCService) generateIDToken(client model.OIDCClientConfig, user
578583
return token, nil
579584
}
580585

581-
func (service *OIDCService) GenerateAccessToken(ctx context.Context, client model.OIDCClientConfig, codeEntry AuthorizeCodeEntry) (*TokenResponse, error) {
582-
idToken, err := service.generateIDToken(client, codeEntry.Userinfo, codeEntry.Scope, codeEntry.Nonce)
586+
func (service *OIDCService) GenerateAccessToken(ctx context.Context, client model.OIDCClientConfig, codeEntry AuthorizeCodeEntry, authTime int64) (*TokenResponse, error) {
587+
idToken, err := service.generateIDToken(client, codeEntry.Userinfo, codeEntry.Scope, codeEntry.Nonce, authTime)
583588

584589
if err != nil {
585590
return nil, err
@@ -660,7 +665,7 @@ func (service *OIDCService) RefreshAccessToken(ctx context.Context, refreshToken
660665

661666
idToken, err := service.generateIDToken(model.OIDCClientConfig{
662667
ClientID: entry.ClientID,
663-
}, userInfo, entry.Scope, entry.Nonce)
668+
}, userInfo, entry.Scope, entry.Nonce, 0) // auth_time is not available during refresh, so we set it to 0
664669

665670
if err != nil {
666671
return nil, err
@@ -929,5 +934,6 @@ func (service *OIDCService) DecodeAuthorizeJWT(tokenString string) (*AuthorizeRe
929934
Nonce: get("nonce"),
930935
CodeChallenge: get("code_challenge"),
931936
CodeChallengeMethod: get("code_challenge_method"),
937+
Prompt: get("prompt"),
932938
}, nil
933939
}

0 commit comments

Comments
 (0)