Skip to content

Commit

Permalink
support authz request on did:jwk
Browse files Browse the repository at this point in the history
  • Loading branch information
gerardsn committed May 13, 2024
1 parent 47165ce commit 66b1d52
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 44 deletions.
56 changes: 22 additions & 34 deletions auth/api/iam/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,18 +323,20 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho
// Workaround: deepmap codegen doesn't support dynamic query parameters.
// See https://github.com/deepmap/oapi-codegen/issues/1129
httpRequest := ctx.Value(httpRequestContextKey{}).(*http.Request)
queryParams := httpRequest.URL.Query()
return r.handleAuthorizeRequest(ctx, *ownDID, *httpRequest.URL)
}

// handleAuthorizeRequest handles calls to the authorization endpoint for starting an authorization code flow.
// ownDID must be validated by the caller
func (r Wrapper) handleAuthorizeRequest(ctx context.Context, ownDID did.DID, request url.URL) (HandleAuthorizeRequestResponseObject, error) {
// parse and validate as JAR (RFC9101, JWT Authorization Request)
authzParams, err := r.jar.Parse(ctx, *ownDID, queryParams)
authzParams, err := r.jar.Parse(ctx, ownDID, request.Query())
if err != nil {
// already an oauth.OAuth2Error
return nil, err
}

session := createSession(authzParams, *ownDID)

switch session.ResponseType {
switch authzParams.get(oauth.ResponseTypeParam) {
case responseTypeCode:
// Options:
// - Regular authorization code flow for EHR data access through access token, authentication of end-user using OpenID4VP.
Expand All @@ -347,10 +349,10 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho
// when client_id is a did:web, it is a cloud/server wallet
// otherwise it's a normal registered client which we do not support yet
// Note: this is the user facing OpenID4VP flow with a "vp_token" responseType, the demo uses the "vp_token id_token" responseType
clientId := session.ClientID
clientId := authzParams.get(oauth.ClientIDParam)
if strings.HasPrefix(clientId, "did:web:") {
// client is a cloud wallet with user
return r.handleAuthorizeRequestFromHolder(ctx, *ownDID, authzParams)
return r.handleAuthorizeRequestFromHolder(ctx, ownDID, authzParams)
} else {
return nil, oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Expand All @@ -364,13 +366,13 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho
// Requests to user wallets can then be rendered as QR-code (or use a cloud wallet).
// Note that it can't be called from the outside, but only by internal dispatch (since Echo doesn't handle openid4vp:, obviously).
walletOwnerType := pe.WalletOwnerOrganization
if strings.HasPrefix(httpRequest.URL.String(), "openid4vp:") {
if strings.HasPrefix(request.String(), "openid4vp:") {
walletOwnerType = pe.WalletOwnerUser
}
return r.handleAuthorizeRequestFromVerifier(ctx, *ownDID, authzParams, walletOwnerType)
return r.handleAuthorizeRequestFromVerifier(ctx, ownDID, authzParams, walletOwnerType)
default:
// TODO: This should be a redirect?
redirectURI, _ := url.Parse(session.RedirectURI)
redirectURI, _ := url.Parse(authzParams.get(oauth.RedirectURIParam))
return nil, oauth.OAuth2Error{
Code: oauth.UnsupportedResponseType,
RedirectURI: redirectURI,
Expand All @@ -394,7 +396,7 @@ func (r Wrapper) GetRequestJWT(ctx context.Context, request GetRequestJWTRequest
return nil, oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Description: "request object not found",
InternalError: errors.New("DID does not match client_id for requestID. Possible authorization RequestObject phishing"),
InternalError: errors.New("DID does not match client_id for requestID"),
}
}
if ro.RequestURIMethod != "get" {
Expand All @@ -406,6 +408,8 @@ func (r Wrapper) GetRequestJWT(ctx context.Context, request GetRequestJWTRequest
InternalError: errors.New("wrong 'request_uri_method' authorization server or wallet probably does not support 'request_uri_method'"),
}
}

// TODO: supported signature types should be checked
token, err := r.jar.Sign(ctx, ro.Claims)
if err != nil {
return nil, oauth.OAuth2Error{
Expand All @@ -432,18 +436,18 @@ func (r Wrapper) PostRequestJWT(ctx context.Context, request PostRequestJWTReque
Description: "request object not found",
}
}
if ro.RequestURIMethod != "post" {
return nil, oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Description: "used request_uri_method 'post' on a 'get' request_uri",
}
}
// compare raw strings, don't waste a db call to see if we own the request.Did.
if ro.Client.String() != request.Did {
return nil, oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Description: "request object not found",
InternalError: errors.New("DID does not match client_id for requestID. Possible authorization RequestObject phishing"),
InternalError: errors.New("DID does not match client_id for requestID"),
}
}
if ro.RequestURIMethod != "post" {
return nil, oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Description: "used request_uri_method 'post' on a 'get' request_uri",
}
}

Expand Down Expand Up @@ -689,22 +693,6 @@ func (r Wrapper) RequestUserAccessToken(ctx context.Context, request RequestUser
}, nil
}

func createSession(params oauthParameters, ownDID did.DID) *OAuthSession {
session := OAuthSession{}
session.ClientID = params.get(oauth.ClientIDParam)
session.Scope = params.get(oauth.ScopeParam)
session.ClientState = params.get(oauth.StateParam)
session.RedirectURI = params.get(oauth.RedirectURIParam)
session.OwnDID = &ownDID
session.ResponseType = params.get(oauth.ResponseTypeParam)
session.PKCEParams = PKCEParams{
Challenge: params.get(oauth.CodeChallengeParam),
ChallengeMethod: params.get(oauth.CodeChallengeMethodParam),
}

return &session
}

func (r Wrapper) StatusList(ctx context.Context, request StatusListRequestObject) (StatusListResponseObject, error) {
requestDID, err := did.ParseDID(request.Did)
if err != nil {
Expand Down
13 changes: 4 additions & 9 deletions auth/api/iam/openid4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ func (r Wrapper) handleAuthorizeRequestFromVerifier(ctx context.Context, tenantD
}

// any error here is a server error, might need a fixup to prevent exposing to a user
return r.sendAndHandleDirectPost(ctx, tenantDID, *vp, *submission, responseURI, state)
return r.sendAndHandleDirectPost(ctx, userSession.Wallet.DID, *vp, *submission, responseURI, state)
}

func (r Wrapper) getClientMetadataFromRequest(ctx context.Context, params oauthParameters) (*oauth.OAuthClientMetadata, *oauth.OAuth2Error) {
Expand Down Expand Up @@ -383,7 +383,7 @@ func (r Wrapper) getPresentationDefinitionFromRequest(ctx context.Context, param

// sendAndHandleDirectPost sends OpenID4VP direct_post to the verifier. The verifier responds with a redirect to the client (including error fields if needed).
// If the direct post fails, the user-agent will be redirected back to the client with an error. (Original redirect_uri).
func (r Wrapper) sendAndHandleDirectPost(ctx context.Context, walletDID did.DID, vp vc.VerifiablePresentation, presentationSubmission pe.PresentationSubmission, verifierResponseURI string, state string) (HandleAuthorizeRequestResponseObject, error) {
func (r Wrapper) sendAndHandleDirectPost(ctx context.Context, userWalletDID did.DID, vp vc.VerifiablePresentation, presentationSubmission pe.PresentationSubmission, verifierResponseURI string, state string) (HandleAuthorizeRequestResponseObject, error) {
redirectURI, err := r.auth.IAMClient().PostAuthorizationResponse(ctx, vp, presentationSubmission, verifierResponseURI, state)
if err != nil {
return nil, err
Expand All @@ -397,13 +397,8 @@ func (r Wrapper) sendAndHandleDirectPost(ctx context.Context, walletDID did.DID,
}
// Dispatch a new HTTP request to the local OpenID4VP wallet's authorization endpoint that includes request parameters,
// but with openid4vp: as scheme.
originalRequest := ctx.Value(httpRequestContextKey{}).(*http.Request)
dispatchHttpRequest := *originalRequest
dispatchHttpRequest.URL = parsedRedirectURI
ctx = context.WithValue(ctx, httpRequestContextKey{}, &dispatchHttpRequest)
response, err := r.HandleAuthorizeRequest(ctx, HandleAuthorizeRequestRequestObject{
Did: walletDID.String(),
})
// The context contains data from the previous request. Usage by the handler will probably result in incorrect behavior.
response, err := r.handleAuthorizeRequest(ctx, userWalletDID, *parsedRedirectURI)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion auth/api/iam/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func (r Wrapper) loadUserSession(cookies CookieReader, tenantDID did.DID, preAut
}
// Note that the session itself does not have an expiration field:
// it depends on the session store to clean up when it expires.
if !session.TenantDID.Equals(tenantDID) {
if !session.TenantDID.Equals(tenantDID) && !session.Wallet.DID.Equals(tenantDID) {
return nil, fmt.Errorf("session belongs to another tenant (%s)", session.TenantDID)
}
// If the existing session was created for a pre-authorized user, the call to RequestUserAccessToken() must be
Expand Down
3 changes: 3 additions & 0 deletions auth/client/iam/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func (hb HTTPClient) PresentationDefinition(ctx context.Context, presentationDef
return &presentationDefinition, hb.doRequest(ctx, request, &presentationDefinition)
}

// RequestObject retrieves the Authorization Request Object from the requestURI using the GET method
func (hb HTTPClient) RequestObject(ctx context.Context, requestURI string) (string, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURI, nil)
if err != nil {
Expand All @@ -111,6 +112,8 @@ func (hb HTTPClient) RequestObject(ctx context.Context, requestURI string) (stri
return string(data), err
}

// RequestObjectPost retrieves the Authorization Request Object from the requestURI using the POST method.
// additional request parameters (wallet_metadata and wallet_nonce) are provided as url.Values.
func (hb HTTPClient) RequestObjectPost(ctx context.Context, requestURI string, form url.Values) (string, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURI, strings.NewReader(form.Encode()))
if err != nil {
Expand Down

0 comments on commit 66b1d52

Please sign in to comment.