• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FIWARE / VCVerifier / 18713115930

22 Oct 2025 10:23AM UTC coverage: 43.214% (-1.0%) from 44.17%
18713115930

push

github

web-flow
Merge pull request #67 from FIWARE/token-exchang

Token exchange

171 of 426 new or added lines in 6 files covered. (40.14%)

3 existing lines in 3 files now uncovered.

1592 of 3684 relevant lines covered (43.21%)

0.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.04
/openapi/api_api.go
1
/*
2
 * vcverifier
3
 *
4
 * Backend component to verify credentials
5
 *
6
 * API version: 0.0.1
7
 * Generated by: OpenAPI Generator (https://openapi-generator.tech)
8
 */
9

10
package openapi
11

12
import (
13
        "encoding/base64"
14
        "encoding/json"
15
        "errors"
16
        "fmt"
17
        "net/http"
18
        "slices"
19
        "strings"
20

21
        "github.com/fiware/VCVerifier/common"
22
        "github.com/fiware/VCVerifier/logging"
23
        "github.com/fiware/VCVerifier/verifier"
24
        "github.com/lestrrat-go/jwx/v3/jwa"
25
        "github.com/lestrrat-go/jwx/v3/jwt"
26
        vdr_jwk "github.com/trustbloc/did-go/method/jwk"
27
        vdr_key "github.com/trustbloc/did-go/method/key"
28
        vdr_web "github.com/trustbloc/did-go/method/web"
29
        "github.com/trustbloc/did-go/vdr/api"
30
        "github.com/trustbloc/vc-go/verifiable"
31

32
        "github.com/gin-gonic/gin"
33
)
34

35
const DEEPLINK = "DEEPLINK"
36
const FRONTEND_V1 = "FRONTEND_V1"
37
const FRONTEND_V2 = "FRONTEND_V2"
38

39
var apiVerifier verifier.Verifier
40
var presentationParser verifier.PresentationParser
41
var sdJwtParser verifier.SdJwtParser
42
var keyResolver verifier.KeyResolver
43

44
var ErrorMessagNoGrantType = ErrorMessage{"no_grant_type_provided", "Token requests require a grant_type."}
45
var ErrorMessageUnsupportedGrantType = ErrorMessage{"unsupported_grant_type", "Provided grant_type is not supported by the implementation."}
46
var ErrorMessageNoCode = ErrorMessage{"no_code_provided", "Token requests require a code."}
47
var ErrorMessageNoRedircetUri = ErrorMessage{"no_redirect_uri_provided", "Token requests require a redirect_uri."}
48
var ErrorMessageNoResource = ErrorMessage{"no_resource_provided", "When using token-exchange, resource is required to provide the client_id."}
49
var ErrorMessageNoState = ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."}
50
var ErrorMessageNoScope = ErrorMessage{"no_scope_provided", "Authentication requires a scope provided as a parameter."}
51
var ErrorMessageInvalidResponseType = ErrorMessage{"invalid_response_type", "Authentication requires the response_type to be `code`."}
52
var ErrorMessageFailedSameDevice = ErrorMessage{"failed_same_device", "Was not able to start a same-device flow."}
53
var ErrorMessageNoClientId = ErrorMessage{"no_client_id_provided", "Authentication requires a client-id provided as a parameter."}
54
var ErrorMessageNoNonce = ErrorMessage{"no_nonce_provided", "Authentication requires a nonce provided as a query parameter."}
55
var ErrorMessageNoToken = ErrorMessage{"no_token_provided", "Authentication requires a token provided as a form parameter."}
56
var ErrorMessageNoPresentationSubmission = ErrorMessage{"no_presentation_submission_provided", "Authentication requires a presentation submission provided as a form parameter."}
57
var ErrorMessageNoCallback = ErrorMessage{"NoCallbackProvided", "A callback address has to be provided as query-parameter."}
58
var ErrorMessageUnableToDecodeToken = ErrorMessage{"invalid_token", "Token could not be decoded."}
59
var ErrorMessageUnableToDecodeCredential = ErrorMessage{"invalid_token", "Could not read the credential(s) inside the token."}
60
var ErrorMessageUnableToDecodeHolder = ErrorMessage{"invalid_token", "Could not read the holder inside the token."}
61
var ErrorMessageNoSuchSession = ErrorMessage{"no_session", "Session with the requested id is not available."}
62
var ErrorMessageInvalidSdJwt = ErrorMessage{"invalid_sdjwt", "SdJwt does not contain all required fields."}
63
var ErrorMessageNoWebsocketConnection = ErrorMessage{"invalid_connection", "No Websocket connection available for the authenticated session."}
64
var ErrorMessageUnresolvableRequestObject = ErrorMessage{"unresolvable_request_object", "Was not able to get the request object from the client."}
65
var ErrorMessageInvalidAudience = ErrorMessage{"invalid_audience", "Audience of the request object was invalid."}
66
var ErrorMessageUnsupportedAssertionType = ErrorMessage{"unsupported_assertion_type", "Assertion type is not supported."}
67
var ErrorMessageInvalidClientAssertion = ErrorMessage{"invalid_client_assertion", "Provided client assertion is invalid."}
68
var ErrorMessageInvalidTokenRequest = ErrorMessage{"invalid_token_request", "Token request has no redirect_uri and no valid client assertion."}
69
var ErrorMessageInvalidSubjectTokenType = ErrorMessage{"invalid_subject_token_type", "Token exchange is only supported for token type urn:eu:oidf:vp_token."}
70
var ErrorMessageInvalidRequestedTokenType = ErrorMessage{"invalid_requested_token_type", "Token exchange is only supported for requesting tokens of type urn:ietf:params:oauth:token-type:access_token."}
71

72
func getApiVerifier() verifier.Verifier {
1✔
73
        if apiVerifier == nil {
1✔
74
                apiVerifier = verifier.GetVerifier()
×
75
        }
×
76
        return apiVerifier
1✔
77
}
78

79
func getPresentationParser() verifier.PresentationParser {
1✔
80
        if presentationParser == nil {
1✔
81
                presentationParser = verifier.GetPresentationParser()
×
82
        }
×
83
        return presentationParser
1✔
84
}
85

86
func getSdJwtParser() verifier.SdJwtParser {
1✔
87
        if sdJwtParser == nil {
1✔
88
                sdJwtParser = verifier.GetSdJwtParser()
×
89
        }
×
90
        return sdJwtParser
1✔
91
}
92

93
func getKeyResolver() verifier.KeyResolver {
×
94
        if keyResolver == nil {
×
95
                keyResolver = &verifier.VdrKeyResolver{Vdr: []api.VDR{vdr_key.New(), vdr_jwk.New(), vdr_web.New()}}
×
96
        }
×
97
        return keyResolver
×
98
}
99

100
// GetToken - Token endpoint to exchange the authorization code with the actual JWT.
101
func GetToken(c *gin.Context) {
1✔
102

1✔
103
        logging.Log().Debugf("%v", c.Request)
1✔
104
        grantType, grantTypeExists := c.GetPostForm("grant_type")
1✔
105
        if !grantTypeExists {
2✔
106
                logging.Log().Debug("No grant_type present in the request.")
1✔
107
                c.AbortWithStatusJSON(400, ErrorMessagNoGrantType)
1✔
108
                return
1✔
109
        }
1✔
110

111
        switch grantType {
1✔
112
        case common.TYPE_CODE:
1✔
113
                handleTokenTypeCode(c)
1✔
114
        case common.TYPE_VP_TOKEN:
1✔
115
                handleTokenTypeVPToken(c, c.GetHeader("client_id"))
1✔
116
        case common.TYPE_TOKEN_EXCHANGE:
1✔
117
                resource, resourceExists := c.GetPostForm("resource")
1✔
118
                if !resourceExists {
2✔
119
                        c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoResource)
1✔
120
                        return
1✔
121
                }
1✔
122
                handleTokenTypeTokenExchange(c, resource)
1✔
123
        default:
1✔
124
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnsupportedGrantType)
1✔
125
        }
126
}
127

NEW
128
func AuthorizationEndpoint(c *gin.Context) {
×
NEW
129
        logging.Log().Debug("Receive authorization request.")
×
NEW
130
        clientId, clientIdExists := c.GetQuery("client_id")
×
NEW
131
        responseType, responseTypeExists := c.GetQuery("response_type")
×
NEW
132
        scope, scopeExists := c.GetQuery("scope")
×
NEW
133
        redirectUri, redirectUriExists := c.GetQuery("redirect_uri")
×
NEW
134
        nonce, nonceExists := c.GetQuery("nonce")
×
NEW
135
        state, stateExists := c.GetQuery("state")
×
NEW
136

×
NEW
137
        requestUri, requestUriExists := c.GetQuery("request_uri")
×
NEW
138
        if requestUriExists {
×
NEW
139
                logging.Log().Debug("Requesting the client for its request object.")
×
NEW
140
                cro, err := getRequestObjectClient().GetClientRequestObject(requestUri)
×
NEW
141
                if err != nil {
×
NEW
142
                        logging.Log().Warnf("Was not able to get request object. Err: %v", err)
×
NEW
143
                        c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessageUnresolvableRequestObject)
×
NEW
144
                        return
×
NEW
145
                }
×
NEW
146
                if !slices.Contains(cro.Aud, getFrontendVerifier().GetHost()) {
×
NEW
147
                        c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessageInvalidAudience)
×
NEW
148
                        return
×
NEW
149
                }
×
150

NEW
151
                if cro.ClientId != "" {
×
NEW
152
                        clientId = cro.ClientId
×
NEW
153
                        clientIdExists = true
×
NEW
154
                }
×
155

NEW
156
                if cro.Scope != "" {
×
NEW
157
                        scope = cro.Scope
×
NEW
158
                        scopeExists = true
×
NEW
159
                }
×
160

NEW
161
                if cro.RedirectUri != "" {
×
NEW
162
                        redirectUri = cro.RedirectUri
×
NEW
163
                        redirectUriExists = true
×
NEW
164
                }
×
165

NEW
166
                if cro.Nonce != "" {
×
NEW
167
                        nonce = cro.Nonce
×
NEW
168
                        nonceExists = true
×
NEW
169
                }
×
170

NEW
171
                if cro.State != "" {
×
NEW
172
                        state = cro.State
×
NEW
173
                        stateExists = true
×
NEW
174
                }
×
175

NEW
176
                if cro.ResponseType != "" {
×
NEW
177
                        responseType = cro.ResponseType
×
NEW
178
                        responseTypeExists = true
×
NEW
179
                }
×
180
        }
181

NEW
182
        if !clientIdExists {
×
NEW
183
                logging.Log().Info("Received an authorization request without a client_id.")
×
NEW
184
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoClientId)
×
NEW
185
                return
×
NEW
186
        }
×
NEW
187
        if !scopeExists {
×
NEW
188
                logging.Log().Info("Received an authorization request without a scope.")
×
NEW
189
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoScope)
×
NEW
190
                return
×
NEW
191
        }
×
NEW
192
        if !redirectUriExists {
×
NEW
193
                logging.Log().Info("Received an authorization request without a redirect_uri.")
×
NEW
194
                //c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoRedircetUri)
×
NEW
195
                //return
×
NEW
196
        }
×
NEW
197
        if !nonceExists {
×
NEW
198
                logging.Log().Info("Received an authorization request without a nonce.")
×
NEW
199
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoNonce)
×
NEW
200
                return
×
NEW
201
        }
×
NEW
202
        if !stateExists {
×
NEW
203
                logging.Log().Info("Received an authorization request without a state.")
×
NEW
204
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
×
NEW
205
                return
×
NEW
206
        }
×
NEW
207
        if !responseTypeExists && responseType != "code" {
×
NEW
208
                logging.Log().Infof("Received an authorization request with an invalid response type. Was %s.", responseType)
×
NEW
209
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidResponseType)
×
NEW
210
                return
×
NEW
211
        }
×
212

NEW
213
        logging.Log().Debugf("Received request: clientID - %s, scope - %s, redirect_uri - %s, nonce - %s, state - %s.", clientId, scope, redirectUri, nonce, state)
×
NEW
214

×
NEW
215
        protocol := "https"
×
NEW
216
        if c.Request.TLS == nil {
×
NEW
217
                protocol = "http"
×
NEW
218
        }
×
219

NEW
220
        authorizationType := getApiVerifier().GetAuthorizationType(clientId)
×
NEW
221
        var redirect string
×
NEW
222
        var err error
×
NEW
223
        switch authorizationType {
×
NEW
224
        case DEEPLINK:
×
NEW
225
                redirect, err = getApiVerifier().StartSameDeviceFlow(c.Request.Host, protocol, state, "", clientId, nonce, verifier.REQUEST_MODE_BY_REFERENCE, scope, verifier.OPENID4VP_PROTOCOL)
×
NEW
226
                if err != nil {
×
NEW
227
                        logging.Log().Warnf("Was not able start a same device flow. Err: %v", err)
×
NEW
228
                        c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessageFailedSameDevice)
×
NEW
229
                        return
×
NEW
230
                }
×
NEW
231
        case FRONTEND_V2:
×
NEW
232
                redirect = buildFrontendV2Address(protocol, c.Request.Host, state, clientId, redirectUri, scope, nonce)
×
233
        }
NEW
234
        c.Redirect(http.StatusFound, redirect)
×
235
}
236

NEW
237
func buildFrontendV2Address(protocol, host, state, clientId, redirectUri, scope, nonce string) string {
×
NEW
238
        return fmt.Sprintf("%s://%s/api/v2/loginQR?state=%s&client_id=%s&redirect_uri=%s&scope=%s&nonce=%s&request_mode=byReference", protocol, host, state, clientId, redirectUri, scope, nonce)
×
UNCOV
239
}
×
240

241
// GetToken - Token endpoint to exchange the authorization code with the actual JWT.
242
func GetTokenForService(c *gin.Context) {
×
243

×
244
        logging.Log().Debugf("%v", c.Request)
×
245
        grantType, grantTypeExists := c.GetPostForm("grant_type")
×
246
        if !grantTypeExists {
×
247
                logging.Log().Debug("No grant_type present in the request.")
×
NEW
248
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessagNoGrantType)
×
249
                return
×
250
        }
×
251

NEW
252
        switch grantType {
×
NEW
253
        case common.TYPE_CODE:
×
254
                handleTokenTypeCode(c)
×
NEW
255
        case common.TYPE_VP_TOKEN:
×
256
                handleTokenTypeVPToken(c, c.Param("service_id"))
×
NEW
257
        case common.TYPE_TOKEN_EXCHANGE:
×
NEW
258
                handleTokenTypeTokenExchange(c, c.Param("service_id"))
×
NEW
259
        default:
×
NEW
260
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnsupportedGrantType)
×
261
        }
262
}
263

264
func handleTokenTypeTokenExchange(c *gin.Context, clientId string) {
1✔
265
        subjectTokenType, subjectTokenTypeExists := c.GetPostForm("subject_token_type")
1✔
266
        if !subjectTokenTypeExists || subjectTokenType != common.TYPE_VP_TOKEN_SUBJECT {
2✔
267
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidSubjectTokenType)
1✔
268
                return
1✔
269
        }
1✔
270
        requestedTokenType, requestedTokenTypeExists := c.GetPostForm("requested_token_type")
1✔
271
        if requestedTokenTypeExists && requestedTokenType != common.TYPE_ACCESS_TOKEN {
2✔
272
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidRequestedTokenType)
1✔
273
                return
1✔
274
        }
1✔
275

276
        scopes := getScopesFromRequest(c)
1✔
277
        if len(scopes) == 0 {
2✔
278
                return
1✔
279
        }
1✔
280

281
        audience, audienceExists := c.GetPostForm("audience")
1✔
282
        if !audienceExists {
2✔
283
                audience = clientId
1✔
284
        }
1✔
285

286
        subjectToken, subjectTokenExists := c.GetPostForm("subject_token")
1✔
287
        if !subjectTokenExists {
2✔
288
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
1✔
289
                return
1✔
290
        }
1✔
291

292
        logging.Log().Debugf("Got token %s", subjectToken)
1✔
293

1✔
294
        verifiyVPToken(c, subjectToken, clientId, scopes, audience)
1✔
295
}
296

297
func handleTokenTypeVPToken(c *gin.Context, clientId string) {
1✔
298

1✔
299
        vpToken, vpTokenExists := c.GetPostForm("vp_token")
1✔
300
        if !vpTokenExists {
2✔
301
                logging.Log().Debug("No vp token present in the request.")
1✔
302
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
1✔
303
                return
1✔
304
        }
1✔
305

306
        logging.Log().Warnf("Got token %s", vpToken)
1✔
307

1✔
308
        scopes := getScopesFromRequest(c)
1✔
309
        if len(scopes) == 0 {
2✔
310
                return
1✔
311
        }
1✔
312

313
        verifiyVPToken(c, vpToken, clientId, scopes, clientId)
1✔
314
}
315

316
func verifiyVPToken(c *gin.Context, vpToken string, clientId string, scopes []string, audience string) {
1✔
317

1✔
318
        presentation, err := extractVpFromToken(c, vpToken)
1✔
319
        if err != nil {
1✔
320
                logging.Log().Warnf("Was not able to extract the credentials from the vp_token. E: %v", err)
×
321
                return
×
322
        }
×
323

324
        logging.Log().Debug("Was able to extract presentation")
1✔
325
        // Subject is empty since multiple VCs with different subjects can be provided
1✔
326
        expiration, signedToken, err := getApiVerifier().GenerateToken(clientId, "", clientId, scopes, presentation)
1✔
327
        if err != nil {
1✔
328
                logging.Log().Error("Failure during generating M2M token: ", err)
×
NEW
329
                c.AbortWithStatusJSON(http.StatusBadRequest, err)
×
330
                return
×
331
        }
×
332
        response := TokenResponse{TokenType: "Bearer", IssuedTokenType: common.TYPE_ACCESS_TOKEN, ExpiresIn: float32(expiration), AccessToken: signedToken, Scope: strings.Join(scopes, ",")}
1✔
333
        logging.Log().Infof("Generated and signed token: %v", response)
1✔
334
        c.JSON(http.StatusOK, response)
1✔
335
}
336

337
func handleTokenTypeCode(c *gin.Context) {
1✔
338

1✔
339
        code, codeExists := c.GetPostForm("code")
1✔
340
        if !codeExists {
2✔
341
                logging.Log().Debug("No code present in the request.")
1✔
342
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoCode)
1✔
343
                return
1✔
344
        }
1✔
345

346
        assertionType, assertionTypeExists := c.GetPostForm("client_assertion_type")
1✔
347
        redirectUri, redirectUriExists := c.GetPostForm("redirect_uri")
1✔
348
        if redirectUriExists {
2✔
349
                jwt, expiration, err := getApiVerifier().GetToken(code, redirectUri, false)
1✔
350
                if err != nil {
2✔
351
                        c.AbortWithStatusJSON(http.StatusForbidden, ErrorMessage{Summary: err.Error()})
1✔
352
                        return
1✔
353
                }
1✔
354
                c.JSON(http.StatusOK, TokenResponse{TokenType: "Bearer", ExpiresIn: float32(expiration), AccessToken: jwt})
1✔
355
                return
1✔
356
        }
357
        if assertionTypeExists {
1✔
358
                handleWithClientAssertion(c, assertionType, code)
×
359
                return
×
360
        }
×
361
        c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidTokenRequest)
1✔
362
}
363

364
func getScopesFromRequest(c *gin.Context) (scopes []string) {
1✔
365

1✔
366
        scope, scopeExists := c.GetPostForm("scope")
1✔
367
        if !scopeExists {
2✔
368
                logging.Log().Debug("No scope present in the request.")
1✔
369
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoScope)
1✔
370
                return scopes
1✔
371
        }
1✔
372

373
        return strings.Split(scope, ",")
1✔
374

375
}
376

377
func handleWithClientAssertion(c *gin.Context, assertionType string, code string) {
×
378
        if assertionType != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" {
×
379
                logging.Log().Warnf("Assertion type %s is not supported.", assertionType)
×
NEW
380
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnsupportedAssertionType)
×
381
                return
×
382
        }
×
383

384
        clientAssertion, clientAssertionExists := c.GetPostForm("client_assertion")
×
385
        clientId, clientIdExists := c.GetPostForm("client_id")
×
386
        if !clientAssertionExists || !clientIdExists {
×
387
                logging.Log().Warnf("Client Id (%s) or assertion (%s) not provided.", clientId, clientAssertion)
×
NEW
388
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnsupportedAssertionType)
×
389
                return
×
390
        }
×
391

392
        kid, err := getKeyResolver().ExtractKIDFromJWT(clientAssertion)
×
393
        if err != nil {
×
394
                logging.Log().Warnf("Was not able to retrive kid from token %s. Err: %v.", clientAssertion, err)
×
NEW
395
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
396
                return
×
397
        }
×
398
        pubKey, err := getKeyResolver().ResolvePublicKeyFromDID(kid)
×
399
        if err != nil {
×
400
                logging.Log().Warnf("Was not able to retrive key from kid %s. Err: %v.", kid, err)
×
NEW
401
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
402
                return
×
403
        }
×
404

405
        alg, algExists := pubKey.Algorithm()
×
406
        if !algExists {
×
407
                // fallback to default
×
408
                alg = jwa.ES256()
×
409
        }
×
410

411
        parsed, err := jwt.Parse([]byte(clientAssertion), jwt.WithKey(alg, pubKey))
×
412
        if err != nil {
×
413
                logging.Log().Warnf("Was not able to parse and verify the token %s. Err: %v", clientAssertion, err)
×
NEW
414
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
415
                return
×
416
        }
×
417

418
        // Serialize token to JSON
419
        jsonBytes, err := json.Marshal(parsed)
×
420
        if err != nil {
×
421
                logging.Log().Warnf("Was not able to marshal the token %s. Err: %v", clientAssertion, err)
×
NEW
422
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
423
                return
×
424
        }
×
425

426
        // Unmarshal to your struct
427
        var clientAssertionObject ClientAssertion
×
428
        if err := json.Unmarshal(jsonBytes, &clientAssertionObject); err != nil {
×
429
                logging.Log().Warnf("Was not able to unmarshal the token: %s, Err: %v", string(jsonBytes), err)
×
NEW
430
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
431
                return
×
432
        }
×
433

434
        if clientAssertionObject.Sub != clientId || clientAssertionObject.Iss != clientId || !slices.Contains(clientAssertionObject.Aud, getFrontendVerifier().GetHost()) {
×
435
                logging.Log().Warnf("Invalid assertion: %s. Client Id: %s, Host: %s", logging.PrettyPrintObject(clientAssertionObject), clientId, getApiVerifier().GetHost())
×
NEW
436
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
437
                return
×
438
        }
×
439

440
        jwt, expiration, err := getApiVerifier().GetToken(code, "", true)
×
441
        if err != nil {
×
NEW
442
                c.AbortWithStatusJSON(http.StatusForbidden, ErrorMessage{Summary: err.Error()})
×
443
                return
×
444
        }
×
445
        c.JSON(http.StatusOK, TokenResponse{TokenType: "Bearer", ExpiresIn: float32(expiration), AccessToken: jwt})
×
446
}
447

448
// StartSIOPSameDevice - Starts the siop flow for credentials hold by the same device
449
func StartSIOPSameDevice(c *gin.Context) {
1✔
450
        state, stateExists := c.GetQuery("state")
1✔
451
        if !stateExists {
2✔
452
                logging.Log().Debugf("No state was provided.")
1✔
453
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."})
1✔
454
                return
1✔
455
        }
1✔
456
        redirectPath, redirectPathExists := c.GetQuery("redirect_path")
1✔
457
        requestProtocol := verifier.REDIRECT_PROTOCOL
1✔
458
        if !redirectPathExists {
2✔
459
                requestProtocol = verifier.OPENID4VP_PROTOCOL
1✔
460
        }
1✔
461

462
        protocol := "https"
1✔
463
        if c.Request.TLS == nil {
2✔
464
                protocol = "http"
1✔
465
        }
1✔
466

467
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
468
        logging.Log().Debugf("The client id %s", clientId)
1✔
469
        if !clientIdExists {
2✔
470
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
471
        }
1✔
472

473
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
474
        if !requestModeExists {
2✔
475
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
476
                requestMode = DEFAULT_REQUEST_MODE
1✔
477
        }
1✔
478

479
        scope, scopeExists := c.GetQuery("scope")
1✔
480
        if !scopeExists {
2✔
481
                logging.Log().Infof("Start a login flow with default scope.")
1✔
482
                scope = ""
1✔
483
        }
1✔
484

485
        authenticationRequest, err := getApiVerifier().StartSameDeviceFlow(c.Request.Host, protocol, state, redirectPath, clientId, "", requestMode, scope, requestProtocol)
1✔
486
        if err != nil {
2✔
487
                logging.Log().Warnf("Error starting the same-device flow. Err: %v", err)
1✔
488
                c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessage{err.Error(), "Was not able to start the same device flow."})
1✔
489
                return
1✔
490
        }
1✔
491

492
        c.Redirect(http.StatusFound, authenticationRequest)
1✔
493
}
494

495
// VerifierAPIAuthenticationResponse - Stores the credential for the given session
496
func VerifierAPIAuthenticationResponse(c *gin.Context) {
1✔
497

1✔
498
        var state string
1✔
499
        stateForm, stateFormExists := c.GetPostForm("state")
1✔
500
        stateQuery, stateQueryExists := c.GetQuery("state")
1✔
501
        if !stateFormExists && !stateQueryExists {
2✔
502
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
1✔
503
                return
1✔
504
        }
1✔
505
        if stateFormExists {
2✔
506
                state = stateForm
1✔
507
        } else {
1✔
508
                // allow the state submitted through a query parameter for backwards-compatibility
×
509
                state = stateQuery
×
510
        }
×
511

512
        vptoken, tokenExists := c.GetPostForm("vp_token")
1✔
513
        if !tokenExists {
2✔
514
                logging.Log().Info("No token was provided.")
1✔
515
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
1✔
516
                return
1✔
517
        }
1✔
518

519
        presentation, err := extractVpFromToken(c, vptoken)
1✔
520
        if err != nil {
2✔
521
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
1✔
522
                return
1✔
523
        }
1✔
524
        handleAuthenticationResponse(c, state, presentation)
1✔
525
}
526

527
// GetVerifierAPIAuthenticationResponse - Stores the credential for the given session
528
func GetVerifierAPIAuthenticationResponse(c *gin.Context) {
×
529
        state, stateExists := c.GetQuery("state")
×
530
        if !stateExists {
×
NEW
531
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
×
532
                return
×
533
        }
×
534

535
        vpToken, tokenExists := c.GetQuery("vp_token")
×
536
        if !tokenExists {
×
537
                logging.Log().Info("No token was provided.")
×
NEW
538
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
×
539
                return
×
540
        }
×
541
        presentation, err := extractVpFromToken(c, vpToken)
×
542
        if err != nil {
×
543
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
×
544
                return
×
545
        }
×
546
        handleAuthenticationResponse(c, state, presentation)
×
547
}
548

549
// GetRequestByReference - Get the request object by reference
550
func GetRequestByReference(c *gin.Context) {
×
551
        sessionId := c.Param("id")
×
552

×
553
        jwt, err := verifier.GetVerifier().GetRequestObject(sessionId)
×
554
        if err != nil {
×
555
                logging.Log().Debugf("No request for  %s. Err: %v", sessionId, err)
×
NEW
556
                c.AbortWithStatusJSON(http.StatusNotFound, ErrorMessageNoSuchSession)
×
557
                return
×
558
        }
×
559
        c.String(http.StatusOK, jwt)
×
560
}
561

562
func extractVpFromToken(c *gin.Context, vpToken string) (parsedPresentation *verifiable.Presentation, err error) {
1✔
563

1✔
564
        logging.Log().Debugf("The token %s.", vpToken)
1✔
565

1✔
566
        parsedPresentation, err = getPresentationFromQuery(c, vpToken)
1✔
567

1✔
568
        if err != nil {
1✔
569
                logging.Log().Debugf("Received a vpToken with a query, but was not able to extract the presentation. Token: %s", vpToken)
×
570
                return parsedPresentation, err
×
571
        }
×
572
        if parsedPresentation != nil {
1✔
573
                return parsedPresentation, err
×
574
        }
×
575
        return tokenToPresentation(c, vpToken)
1✔
576

577
}
578

579
func tokenToPresentation(c *gin.Context, vpToken string) (parsedPresentation *verifiable.Presentation, err error) {
1✔
580
        tokenBytes := decodeVpString(vpToken)
1✔
581

1✔
582
        isSdJWT, parsedPresentation, err := isSdJWT(c, vpToken)
1✔
583
        if isSdJWT && err != nil {
1✔
584
                return
×
585
        }
×
586
        if isSdJWT {
2✔
587
                logging.Log().Debugf("Received an sdJwt: %s", logging.PrettyPrintObject(parsedPresentation))
1✔
588
                return
1✔
589
        }
1✔
590

591
        parsedPresentation, err = getSdJwtParser().ParseWithSdJwt(tokenBytes)
1✔
592
        if err == nil {
1✔
NEW
593
                logging.Log().Debug("Parsed presentation containing sd-jwt's.")
×
NEW
594
                return parsedPresentation, err
×
NEW
595
        }
×
596
        if err == verifier.ErrorInvalidProof {
1✔
NEW
597
                logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
×
NEW
598
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnableToDecodeToken)
×
NEW
599
                return
×
NEW
600
        }
×
601
        logging.Log().Debugf("Parse without SD-Jwt %v", err)
1✔
602

1✔
603
        logging.Log().Debug("Parse presentation.")
1✔
604

1✔
605
        parsedPresentation, err = getPresentationParser().ParsePresentation(tokenBytes)
1✔
606

1✔
607
        if err != nil {
2✔
608
                logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
1✔
609
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnableToDecodeToken)
1✔
610
                return
1✔
611
        }
1✔
612

613
        return
1✔
614
}
615

616
func getPresentationFromQuery(c *gin.Context, vpToken string) (parsedPresentation *verifiable.Presentation, err error) {
1✔
617
        tokenBytes := decodeVpString(vpToken)
1✔
618

1✔
619
        var queryMap map[string]string
1✔
620
        //unmarshal
1✔
621
        err = json.Unmarshal(tokenBytes, &queryMap)
1✔
622
        if err != nil {
2✔
623
                logging.Log().Debug("VP Token does not contain query map. Checking the other options.", err)
1✔
624
                return nil, nil
1✔
625
        }
1✔
626

627
        for _, v := range queryMap {
×
628
                p, err := tokenToPresentation(c, v)
×
629
                if err != nil {
×
630
                        return nil, err
×
631
                }
×
632
                if parsedPresentation == nil {
×
633
                        parsedPresentation = p
×
634
                } else {
×
635
                        parsedPresentation.AddCredentials(p.Credentials()...)
×
636
                }
×
637
        }
638
        return parsedPresentation, err
×
639
}
640

641
// checks if the presented token contains a single sd-jwt credential. Will be repackage to a presentation for further validation
642
func isSdJWT(c *gin.Context, vpToken string) (isSdJwt bool, presentation *verifiable.Presentation, err error) {
1✔
643
        claims, err := getSdJwtParser().Parse(vpToken)
1✔
644
        if err != nil {
2✔
645
                logging.Log().Debugf("Was not a sdjwt. Err: %v", err)
1✔
646
                return false, presentation, err
1✔
647
        }
1✔
648
        issuer, i_ok := claims["iss"]
1✔
649
        vct, vct_ok := claims["vct"]
1✔
650
        if !i_ok || !vct_ok {
1✔
651
                logging.Log().Infof("Token does not contain issuer(%v) or vct(%v).", i_ok, vct_ok)
×
NEW
652
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidSdJwt)
×
653
                return true, presentation, errors.New(ErrorMessageInvalidSdJwt.Summary)
×
654
        }
×
655
        customFields := verifiable.CustomFields{}
1✔
656
        for k, v := range claims {
2✔
657
                if k != "iss" && k != "vct" {
2✔
658
                        customFields[k] = v
1✔
659
                }
1✔
660
        }
661
        subject := verifiable.Subject{CustomFields: customFields}
1✔
662
        contents := verifiable.CredentialContents{Issuer: &verifiable.Issuer{ID: issuer.(string)}, Types: []string{vct.(string)}, Subject: []verifiable.Subject{subject}}
1✔
663
        credential, err := verifiable.CreateCredential(contents, verifiable.CustomFields{})
1✔
664
        if err != nil {
1✔
665
                logging.Log().Infof("Was not able to create credential from sdJwt. E: %v", err)
×
NEW
666
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidSdJwt)
×
667
                return true, presentation, err
×
668
        }
×
669
        presentation, err = verifiable.NewPresentation()
1✔
670
        if err != nil {
1✔
671
                logging.Log().Infof("Was not able to create credpresentation from sdJwt. E: %v", err)
×
NEW
672
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidSdJwt)
×
673
                return true, presentation, err
×
674
        }
×
675
        presentation.AddCredentials(credential)
1✔
676
        presentation.Holder = issuer.(string)
1✔
677
        return true, presentation, nil
1✔
678
}
679

680
// decodeVpString - In newer versions of OID4VP the token is not encoded as a whole but only its segments separately. This function covers the older and newer versions
681
func decodeVpString(vpToken string) (tokenBytes []byte) {
1✔
682
        tokenBytes, err := base64.RawURLEncoding.DecodeString(vpToken)
1✔
683
        if err != nil {
2✔
684
                return []byte(vpToken)
1✔
685
        }
1✔
686
        return tokenBytes
1✔
687
}
688

689
func handleAuthenticationResponse(c *gin.Context, state string, presentation *verifiable.Presentation) {
1✔
690

1✔
691
        response, err := getApiVerifier().AuthenticationResponse(state, presentation)
1✔
692
        if err != nil {
2✔
693
                logging.Log().Warnf("Was not able to fullfil the authentication response. Err: %v", err)
1✔
694
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessage{Summary: err.Error()})
1✔
695
                return
1✔
696
        }
1✔
697
        if response != (verifier.Response{}) && response.FlowVersion == verifier.SAME_DEVICE {
2✔
698
                if response.Nonce != "" {
1✔
699
                        c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s&nonce=%s", response.RedirectTarget, response.SessionId, response.Code, response.Nonce))
×
700
                } else {
1✔
701
                        c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s", response.RedirectTarget, response.SessionId, response.Code))
1✔
702
                }
1✔
703
                return
1✔
704
        } else if response != (verifier.Response{}) && response.FlowVersion == verifier.CROSS_DEVICE_V2 {
×
705
                sendRedirect(c, response.SessionId, response.Code, response.RedirectTarget)
×
706
        }
×
707
        logging.Log().Debugf("Successfully authenticated %s.", state)
×
708
        c.JSON(http.StatusOK, gin.H{})
×
709
}
710

711
// VerifierAPIJWKS - Provides the public keys for the given verifier, to be used for verifing the JWTs
712
func VerifierAPIJWKS(c *gin.Context) {
×
713
        c.JSON(http.StatusOK, getApiVerifier().GetJWKS())
×
714
}
×
715

716
// VerifierAPIOpenID
717
func VerifierAPIOpenIDConfiguration(c *gin.Context) {
×
718

×
719
        metadata, err := getApiVerifier().GetOpenIDConfiguration(c.Param("service_id"))
×
720
        if err != nil {
×
NEW
721
                c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessage{err.Error(), "Was not able to generate the OpenID metadata."})
×
722
                return
×
723
        }
×
724
        c.JSON(http.StatusOK, metadata)
×
725
}
726

727
// VerifierAPIStartSIOP - Initiates the siop flow and returns the 'openid://...' connection string
728
func VerifierAPIStartSIOP(c *gin.Context) {
1✔
729
        state, stateExists := c.GetQuery("state")
1✔
730
        if !stateExists {
2✔
731
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
1✔
732
                // early exit
1✔
733
                return
1✔
734
        }
1✔
735

736
        callback, callbackExists := c.GetQuery("client_callback")
1✔
737
        if !callbackExists {
2✔
738
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoCallback)
1✔
739
                // early exit
1✔
740
                return
1✔
741
        }
1✔
742
        protocol := "https"
1✔
743
        if c.Request.TLS == nil {
2✔
744
                protocol = "http"
1✔
745
        }
1✔
746
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
747
        if !clientIdExists {
2✔
748
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
749
        }
1✔
750

751
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
752
        if !requestModeExists {
2✔
753
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
754
                requestMode = DEFAULT_REQUEST_MODE
1✔
755
        }
1✔
756

757
        connectionString, err := getApiVerifier().StartSiopFlow(c.Request.Host, protocol, callback, state, clientId, "", requestMode)
1✔
758
        if err != nil {
2✔
759
                c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessage{err.Error(), "Was not able to generate the connection string."})
1✔
760
                return
1✔
761
        }
1✔
762
        c.String(http.StatusOK, connectionString)
1✔
763
}
764

765
type ClientAssertion struct {
766
        Iss string   `json:"iss"`
767
        Aud []string `json:"aud"`
768
        Sub string   `json:"sub"`
769
        Exp int      `json:"exp"`
770
        Iat int      `json:"iat"`
771
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc