Skip to content

Commit

Permalink
Create new refresh token in the original offline grant
Browse files Browse the repository at this point in the history
ref DEV-1406
  • Loading branch information
louischan-oursky committed Jul 8, 2024
2 parents 4f65d76 + 0916285 commit e2dcef5
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 69 deletions.
4 changes: 3 additions & 1 deletion pkg/lib/oauth/grant_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (

"github.com/authgear/authgear-server/pkg/lib/authn/authenticationinfo"
"github.com/authgear/authgear-server/pkg/lib/oauth/protocol"
"github.com/authgear/authgear-server/pkg/lib/session"
)

type CodeGrant struct {
AppID string `json:"app_id"`
AuthorizationID string `json:"authz_id"`
IDPSessionID string `json:"session_id"`
SessionType session.Type `json:"session_type"`
SessionID string `json:"session_id"`
AuthenticationInfo authenticationinfo.T `json:"authentication_info"`
IDTokenHintSID string `json:"id_token_hint_sid"`

Expand Down
33 changes: 20 additions & 13 deletions pkg/lib/oauth/handler/handler_authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,10 @@ func (h *AuthorizationHandler) doHandle(
authenticationInfo := resolvedSession.GetAuthenticationInfo()
autoGrantAuthz := client.IsFirstParty()

idpSessionID := ""
if resolvedSession.SessionType() == session.TypeIdentityProvider {
idpSessionID = resolvedSession.SessionID()
}
sessionType := resolvedSession.SessionType()
sessionID := resolvedSession.SessionID()

result, err := h.finish(redirectURI, r, idpSessionID, authenticationInfo, idTokenHintSID, nil, autoGrantAuthz)
result, err := h.finish(redirectURI, r, sessionType, sessionID, authenticationInfo, idTokenHintSID, nil, autoGrantAuthz)
if err != nil {
if errors.Is(err, oauth.ErrAuthorizationNotFound) {
return nil, protocol.NewError("access_denied", "authorization required")
Expand Down Expand Up @@ -562,7 +560,8 @@ func (h *AuthorizationHandler) doHandleAppInitiatedSSOToWeb(
func (h *AuthorizationHandler) finish(
redirectURI *url.URL,
r protocol.AuthorizationRequest,
idpSessionID string,
sessionType session.Type,
sessionID string,
authenticationInfo authenticationinfo.T,
idTokenHintSID string,
cookies []*http.Cookie,
Expand Down Expand Up @@ -591,13 +590,17 @@ func (h *AuthorizationHandler) finish(
responseType := r.ResponseType()
switch {
case responseType.Equal(SettingsActonResponseType):
idpSessionID := ""
if sessionType == session.TypeIdentityProvider {
idpSessionID = sessionID
}
err = h.generateSettingsActionResponse(redirectURI.String(), idpSessionID, authenticationInfo, idTokenHintSID, r, authz, resp)
if err != nil {
return nil, err
}

case responseType.Equal(CodeResponseType):
err = h.generateCodeResponse(redirectURI.String(), idpSessionID, authenticationInfo, idTokenHintSID, r, authz, resp)
err = h.generateCodeResponse(redirectURI.String(), sessionType, sessionID, authenticationInfo, idTokenHintSID, r, authz, resp)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -645,12 +648,14 @@ func (h *AuthorizationHandler) doHandleConsentRequest(
}
idTokenHintSID := uiInfoByProduct.IDTokenHintSID

var idpSessionID string
if s := session.GetSession(h.Context); s != nil && s.SessionType() == session.TypeIdentityProvider {
idpSessionID = s.SessionID()
sessionID := ""
var sessionType session.Type = ""
if s := session.GetSession(h.Context); s != nil {
sessionID = s.SessionID()
sessionType = s.SessionType()
}

return h.finish(redirectURI, r, idpSessionID, authenticationInfo, idTokenHintSID, []*http.Cookie{}, grantAuthz)
return h.finish(redirectURI, r, sessionType, sessionID, authenticationInfo, idTokenHintSID, []*http.Cookie{}, grantAuthz)
}

func (h *AuthorizationHandler) validateAppInitiatedSSOToWebTokenRequest(
Expand Down Expand Up @@ -746,7 +751,8 @@ func (h *AuthorizationHandler) validateRequest(

func (h *AuthorizationHandler) generateCodeResponse(
redirectURI string,
idpSessionID string,
sessionType session.Type,
sessionID string,
authenticationInfo authenticationinfo.T,
idTokenHintSID string,
r protocol.AuthorizationRequest,
Expand All @@ -755,7 +761,8 @@ func (h *AuthorizationHandler) generateCodeResponse(
) error {
code, _, err := h.CodeGrantService.CreateCodeGrant(&CreateCodeGrantOptions{
Authorization: authz,
IDPSessionID: idpSessionID,
SessionType: sessionType,
SessionID: sessionID,
AuthenticationInfo: authenticationInfo,
IDTokenHintSID: idTokenHintSID,
RedirectURI: redirectURI,
Expand Down
7 changes: 5 additions & 2 deletions pkg/lib/oauth/handler/handler_authz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/authgear/authgear-server/pkg/lib/oauth/handler"
"github.com/authgear/authgear-server/pkg/lib/oauth/oidc"
"github.com/authgear/authgear-server/pkg/lib/oauth/protocol"
"github.com/authgear/authgear-server/pkg/lib/session"
sessiontest "github.com/authgear/authgear-server/pkg/lib/session/test"
"github.com/authgear/authgear-server/pkg/util/clock"
)
Expand Down Expand Up @@ -293,7 +294,8 @@ func TestAuthorizationHandler(t *testing.T) {
So(codeGrantStore.grants[0], ShouldResemble, oauth.CodeGrant{
AppID: "app-id",
AuthorizationID: authorization.ID,
IDPSessionID: "session-id",
SessionType: session.TypeIdentityProvider,
SessionID: "session-id",
AuthenticationInfo: authenticationinfo.T{
UserID: "user-id",
},
Expand Down Expand Up @@ -345,7 +347,8 @@ func TestAuthorizationHandler(t *testing.T) {
So(codeGrantStore.grants[0], ShouldResemble, oauth.CodeGrant{
AppID: "app-id",
AuthorizationID: "authz-id",
IDPSessionID: "session-id",
SessionType: session.TypeIdentityProvider,
SessionID: "session-id",
AuthenticationInfo: authenticationinfo.T{
UserID: "user-id",
},
Expand Down
62 changes: 40 additions & 22 deletions pkg/lib/oauth/handler/handler_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ type TokenHandlerTokenService interface {
opts IssueOfflineGrantOptions,
resp protocol.TokenResponse,
) (offlineGrant *oauth.OfflineGrant, tokenHash string, err error)
IssueRefreshTokenForOfflineGrant(
offlineGrantID string,
client *config.OAuthClientConfig,
opts IssueOfflineGrantRefreshTokenOptions,
resp protocol.TokenResponse,
) (offlineGrant *oauth.OfflineGrant, tokenHash string, err error)
IssueDeviceSecret(resp protocol.TokenResponse) (deviceSecretHash string)
}

Expand Down Expand Up @@ -1282,9 +1288,16 @@ func (h *TokenHandler) handleApp2AppRequest(
artificialAuthorizationRequest["x_sso_enabled"] = "true"
}

originalIDPSessionID := originalOfflineGrant.IDPSessionID
var sessionType session.Type = ""
if originalIDPSessionID != "" {
sessionType = session.TypeIdentityProvider
}

code, _, err := h.CodeGrantService.CreateCodeGrant(&CreateCodeGrantOptions{
Authorization: authz,
IDPSessionID: originalOfflineGrant.IDPSessionID,
SessionType: sessionType,
SessionID: originalIDPSessionID,
AuthenticationInfo: info,
IDTokenHintSID: "",
RedirectURI: redirectURI.String(),
Expand Down Expand Up @@ -1485,22 +1498,39 @@ func (h *TokenHandler) doIssueTokensForAuthorizationCode(
Scopes: scopes,
AuthorizationID: authz.ID,
AuthenticationInfo: info,
IDPSessionID: code.IDPSessionID,
IDPSessionID: code.SessionID,
DeviceInfo: deviceInfo,
SSOEnabled: code.AuthorizationRequest.SSOEnabled(),
App2AppDeviceKey: app2appDevicePublicKey,
IssueDeviceSecret: issueDeviceToken,
}
if issueRefreshToken {
offlineGrant, tokenHash, err := h.issueOfflineGrant(
client,
code.AuthenticationInfo.UserID,
resp,
opts,
true)
if err != nil {
return nil, err
var offlineGrant *oauth.OfflineGrant
var tokenHash string
var err error
switch code.SessionType {
case session.TypeOfflineGrant:
offlineGrant, tokenHash, err = h.TokenService.IssueRefreshTokenForOfflineGrant(code.SessionID, client, IssueOfflineGrantRefreshTokenOptions{
Scopes: scopes,
AuthorizationID: authz.ID,
}, resp)
if err != nil {
return nil, err
}
case session.TypeIdentityProvider:
fallthrough
default:
offlineGrant, tokenHash, err = h.issueOfflineGrant(
client,
code.AuthenticationInfo.UserID,
resp,
opts,
true)
if err != nil {
return nil, err
}
}

sid = oidc.EncodeSID(offlineGrant)
accessTokenSessionID = offlineGrant.ID
accessTokenSessionKind = oauth.GrantSessionKindOffline
Expand Down Expand Up @@ -1646,18 +1676,6 @@ func (h *TokenHandler) issueTokensForRefreshToken(
return resp, nil
}

type IssueOfflineGrantOptions struct {
AuthenticationInfo authenticationinfo.T
Scopes []string
AuthorizationID string
IDPSessionID string
DeviceInfo map[string]interface{}
IdentityID string
SSOEnabled bool
App2AppDeviceKey jwk.Key
IssueDeviceSecret bool
}

func (h *TokenHandler) IssueAppSessionToken(refreshToken string) (string, *oauth.AppSessionToken, error) {
authz, grant, refreshTokenHash, err := h.TokenService.ParseRefreshToken(refreshToken)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions pkg/lib/oauth/handler/handler_token_mock_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions pkg/lib/oauth/handler/service_code_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/authgear/authgear-server/pkg/lib/config"
"github.com/authgear/authgear-server/pkg/lib/oauth"
"github.com/authgear/authgear-server/pkg/lib/oauth/protocol"
"github.com/authgear/authgear-server/pkg/lib/session"
"github.com/authgear/authgear-server/pkg/util/clock"
)

Expand All @@ -18,7 +19,8 @@ type CodeGrantService struct {

type CreateCodeGrantOptions struct {
Authorization *oauth.Authorization
IDPSessionID string
SessionType session.Type
SessionID string
AuthenticationInfo authenticationinfo.T
IDTokenHintSID string
RedirectURI string
Expand All @@ -32,7 +34,8 @@ func (s *CodeGrantService) CreateCodeGrant(opts *CreateCodeGrantOptions) (code s
codeGrant := &oauth.CodeGrant{
AppID: string(s.AppID),
AuthorizationID: opts.Authorization.ID,
IDPSessionID: opts.IDPSessionID,
SessionType: opts.SessionType,
SessionID: opts.SessionID,
AuthenticationInfo: opts.AuthenticationInfo,
IDTokenHintSID: opts.IDTokenHintSID,

Expand Down
46 changes: 46 additions & 0 deletions pkg/lib/oauth/handler/service_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"encoding/json"
"errors"

"github.com/lestrrat-go/jwx/v2/jwk"

"github.com/authgear/authgear-server/pkg/lib/authn/authenticationinfo"
"github.com/authgear/authgear-server/pkg/lib/authn/user"
"github.com/authgear/authgear-server/pkg/lib/config"
"github.com/authgear/authgear-server/pkg/lib/oauth"
Expand All @@ -17,6 +20,23 @@ import (

var ErrInvalidRefreshToken = protocol.NewError("invalid_grant", "invalid refresh token")

type IssueOfflineGrantOptions struct {
AuthenticationInfo authenticationinfo.T
Scopes []string
AuthorizationID string
IDPSessionID string
DeviceInfo map[string]interface{}
IdentityID string
SSOEnabled bool
App2AppDeviceKey jwk.Key
IssueDeviceSecret bool
}

type IssueOfflineGrantRefreshTokenOptions struct {
Scopes []string
AuthorizationID string
}

type TokenService struct {
RemoteIP httputil.RemoteIP
UserAgentString httputil.UserAgentString
Expand Down Expand Up @@ -112,6 +132,32 @@ func (s *TokenService) IssueOfflineGrant(
return offlineGrant, tokenHash, nil
}

func (s *TokenService) IssueRefreshTokenForOfflineGrant(
offlineGrantID string,
client *config.OAuthClientConfig,
opts IssueOfflineGrantRefreshTokenOptions,
resp protocol.TokenResponse,
) (offlineGrant *oauth.OfflineGrant, tokenHash string, err error) {
offlineGrant, err = s.OfflineGrants.GetOfflineGrant(offlineGrantID)
if err != nil {
return nil, "", err
}

newRefreshTokenResult, newOfflineGrant, err := s.OfflineGrantService.CreateNewRefreshToken(
offlineGrant, client.ClientID, opts.Scopes, opts.AuthorizationID,
)
if err != nil {
return nil, "", err
}
offlineGrant = newOfflineGrant

if resp != nil {
resp.RefreshToken(oauth.EncodeRefreshToken(newRefreshTokenResult.Token, offlineGrant.ID))
}

return newOfflineGrant, newRefreshTokenResult.TokenHash, nil
}

func (s *TokenService) IssueAccessGrant(
client *config.OAuthClientConfig,
scopes []string,
Expand Down
Loading

0 comments on commit e2dcef5

Please sign in to comment.