feat(op): ID token for device authorization grant (#500)
This commit is contained in:
parent
7bdaf9c71d
commit
b300027cd7
5 changed files with 162 additions and 42 deletions
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
strs "github.com/zitadel/oidc/v3/pkg/strings"
|
||||
)
|
||||
|
||||
type DeviceAuthorizationConfig struct {
|
||||
|
@ -185,24 +186,6 @@ func NewUserCode(charSet []rune, charAmount, dashInterval int) (string, error) {
|
|||
return buf.String(), nil
|
||||
}
|
||||
|
||||
type deviceAccessTokenRequest struct {
|
||||
subject string
|
||||
audience []string
|
||||
scopes []string
|
||||
}
|
||||
|
||||
func (r *deviceAccessTokenRequest) GetSubject() string {
|
||||
return r.subject
|
||||
}
|
||||
|
||||
func (r *deviceAccessTokenRequest) GetAudience() []string {
|
||||
return r.audience
|
||||
}
|
||||
|
||||
func (r *deviceAccessTokenRequest) GetScopes() []string {
|
||||
return r.scopes
|
||||
}
|
||||
|
||||
func DeviceAccessToken(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
|
||||
ctx, span := tracer.Start(r.Context(), "DeviceAccessToken")
|
||||
defer span.End()
|
||||
|
@ -229,7 +212,7 @@ func deviceAccessToken(w http.ResponseWriter, r *http.Request, exchanger Exchang
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, err := CheckDeviceAuthorizationState(ctx, clientID, req.DeviceCode, exchanger)
|
||||
tokenRequest, err := CheckDeviceAuthorizationState(ctx, clientID, req.DeviceCode, exchanger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -243,11 +226,6 @@ func deviceAccessToken(w http.ResponseWriter, r *http.Request, exchanger Exchang
|
|||
WithDescription("confidential client requires authentication")
|
||||
}
|
||||
|
||||
tokenRequest := &deviceAccessTokenRequest{
|
||||
subject: state.Subject,
|
||||
audience: []string{clientID},
|
||||
scopes: state.Scopes,
|
||||
}
|
||||
resp, err := CreateDeviceTokenResponse(r.Context(), tokenRequest, exchanger, client)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -265,6 +243,50 @@ func ParseDeviceAccessTokenRequest(r *http.Request, exchanger Exchanger) (*oidc.
|
|||
return req, nil
|
||||
}
|
||||
|
||||
// DeviceAuthorizationState describes the current state of
|
||||
// the device authorization flow.
|
||||
// It implements the [IDTokenRequest] interface.
|
||||
type DeviceAuthorizationState struct {
|
||||
ClientID string
|
||||
Audience []string
|
||||
Scopes []string
|
||||
Expires time.Time // The time after we consider the authorization request timed-out
|
||||
Done bool // The user authenticated and approved the authorization request
|
||||
Denied bool // The user authenticated and denied the authorization request
|
||||
|
||||
// The following fields are populated after Done == true
|
||||
Subject string
|
||||
AMR []string
|
||||
AuthTime time.Time
|
||||
}
|
||||
|
||||
func (r *DeviceAuthorizationState) GetAMR() []string {
|
||||
return r.AMR
|
||||
}
|
||||
|
||||
func (r *DeviceAuthorizationState) GetAudience() []string {
|
||||
if !strs.Contains(r.Audience, r.ClientID) {
|
||||
r.Audience = append(r.Audience, r.ClientID)
|
||||
}
|
||||
return r.Audience
|
||||
}
|
||||
|
||||
func (r *DeviceAuthorizationState) GetAuthTime() time.Time {
|
||||
return r.AuthTime
|
||||
}
|
||||
|
||||
func (r *DeviceAuthorizationState) GetClientID() string {
|
||||
return r.ClientID
|
||||
}
|
||||
|
||||
func (r *DeviceAuthorizationState) GetScopes() []string {
|
||||
return r.Scopes
|
||||
}
|
||||
|
||||
func (r *DeviceAuthorizationState) GetSubject() string {
|
||||
return r.Subject
|
||||
}
|
||||
|
||||
func CheckDeviceAuthorizationState(ctx context.Context, clientID, deviceCode string, exchanger Exchanger) (*DeviceAuthorizationState, error) {
|
||||
storage, err := assertDeviceStorage(exchanger.Storage())
|
||||
if err != nil {
|
||||
|
@ -291,15 +313,32 @@ func CheckDeviceAuthorizationState(ctx context.Context, clientID, deviceCode str
|
|||
}
|
||||
|
||||
func CreateDeviceTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator, client Client) (*oidc.AccessTokenResponse, error) {
|
||||
/* TODO(v4):
|
||||
Change the TokenRequest argument type to *DeviceAuthorizationState.
|
||||
Breaking change that can not be done for v3.
|
||||
*/
|
||||
ctx, span := tracer.Start(ctx, "CreateDeviceTokenResponse")
|
||||
defer span.End()
|
||||
|
||||
accessToken, refreshToken, validity, err := CreateAccessToken(ctx, tokenRequest, client.AccessTokenType(), creator, client, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oidc.AccessTokenResponse{
|
||||
response := &oidc.AccessTokenResponse{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
TokenType: oidc.BearerToken,
|
||||
ExpiresIn: uint64(validity.Seconds()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO(v4): remove type assertion
|
||||
if idTokenRequest, ok := tokenRequest.(IDTokenRequest); ok && strs.Contains(tokenRequest.GetScopes(), oidc.ScopeOpenID) {
|
||||
response.IDToken, err = CreateIDToken(ctx, IssuerFromContext(ctx), idTokenRequest, client.IDTokenLifetime(), accessToken, "", creator.Storage(), client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue