forked from casdoor/casdoor
feat: add support for OAuth 2.0 DPoP (Demonstrating Proof of Possession)
This commit is contained in:
@@ -250,6 +250,9 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract DPoP proof header (RFC 9449). Empty string when DPoP is not used.
|
||||||
|
dpopProof := c.Ctx.Request.Header.Get("DPoP")
|
||||||
|
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
if deviceCode != "" {
|
if deviceCode != "" {
|
||||||
deviceAuthCache, ok := object.DeviceAuthMap.Load(deviceCode)
|
deviceAuthCache, ok := object.DeviceAuthMap.Load(deviceCode)
|
||||||
@@ -291,7 +294,7 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
username = deviceAuthCacheCast.UserName
|
username = deviceAuthCacheCast.UserName
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage(), subjectToken, subjectTokenType, assertion, clientAssertion, clientAssertionType, audience, resource)
|
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage(), subjectToken, subjectTokenType, assertion, clientAssertion, clientAssertionType, audience, resource, dpopProof)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -340,7 +343,8 @@ func (c *ApiController) RefreshToken() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshToken2, err := object.RefreshToken(application, grantType, refreshToken, scope, clientId, clientSecret, host)
|
dpopProof := c.Ctx.Request.Header.Get("DPoP")
|
||||||
|
refreshToken2, err := object.RefreshToken(application, grantType, refreshToken, scope, clientId, clientSecret, host, dpopProof)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -556,6 +560,11 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
|
|
||||||
introspectionResponse.TokenType = token.TokenType
|
introspectionResponse.TokenType = token.TokenType
|
||||||
introspectionResponse.ClientId = application.ClientId
|
introspectionResponse.ClientId = application.ClientId
|
||||||
|
|
||||||
|
// Expose DPoP key binding in the introspection response (RFC 9449 §8).
|
||||||
|
if token.DPoPJkt != "" {
|
||||||
|
introspectionResponse.Cnf = &object.DPoPConfirmation{JKT: token.DPoPJkt}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = introspectionResponse
|
c.Data["json"] = introspectionResponse
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ type Token struct {
|
|||||||
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
||||||
CodeIsUsed bool `json:"codeIsUsed"`
|
CodeIsUsed bool `json:"codeIsUsed"`
|
||||||
CodeExpireIn int64 `json:"codeExpireIn"`
|
CodeExpireIn int64 `json:"codeExpireIn"`
|
||||||
Resource string `xorm:"varchar(255)" json:"resource"` // RFC 8707 Resource Indicator
|
Resource string `xorm:"varchar(255)" json:"resource"` // RFC 8707 Resource Indicator
|
||||||
|
DPoPJkt string `xorm:"varchar(255) 'dpop_jkt'" json:"dPoPJkt"` // RFC 9449 DPoP JWK thumbprint binding
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTokenCount(owner, organization, field, value string) (int64, error) {
|
func GetTokenCount(owner, organization, field, value string) (int64, error) {
|
||||||
@@ -235,3 +236,9 @@ func ExpireTokenByUser(owner, username string) (bool, error) {
|
|||||||
|
|
||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateTokenDPoP updates the token_type and dpop_jkt columns for DPoP binding (RFC 9449).
|
||||||
|
func updateTokenDPoP(token *Token) error {
|
||||||
|
_, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("token_type", "dpop_jkt").Update(token)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
157
object/token_dpop.go
Normal file
157
object/token_dpop.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v4"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dpopMaxAgeSeconds = 300
|
||||||
|
|
||||||
|
// DPoPProofClaims represents the payload claims of a DPoP proof JWT (RFC 9449).
|
||||||
|
type DPoPProofClaims struct {
|
||||||
|
Jti string `json:"jti"`
|
||||||
|
Htm string `json:"htm"`
|
||||||
|
Htu string `json:"htu"`
|
||||||
|
Ath string `json:"ath,omitempty"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateDPoPProof validates a DPoP proof JWT as specified in RFC 9449.
|
||||||
|
//
|
||||||
|
// - proofToken: the compact-serialized DPoP proof JWT from the DPoP HTTP header
|
||||||
|
// - method: the HTTP request method (e.g., "POST", "GET")
|
||||||
|
// - htu: the HTTP request URL without query string or fragment
|
||||||
|
// - accessToken: the access token string; empty at the token endpoint,
|
||||||
|
// non-empty at protected resource endpoints (enables ath claim validation)
|
||||||
|
//
|
||||||
|
// On success it returns the base64url-encoded SHA-256 JWK thumbprint (jkt) of
|
||||||
|
// the DPoP public key embedded in the proof header.
|
||||||
|
func ValidateDPoPProof(proofToken, method, htu, accessToken string) (string, error) {
|
||||||
|
parts := strings.Split(proofToken, ".")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return "", fmt.Errorf("invalid DPoP proof JWT format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode and inspect the JOSE header before signature verification.
|
||||||
|
headerBytes, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode DPoP proof header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var header struct {
|
||||||
|
Typ string `json:"typ"`
|
||||||
|
Alg string `json:"alg"`
|
||||||
|
JWK json.RawMessage `json:"jwk"`
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(headerBytes, &header); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse DPoP proof header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// typ MUST be exactly "dpop+jwt" (RFC 9449 §4.2).
|
||||||
|
if header.Typ != "dpop+jwt" {
|
||||||
|
return "", fmt.Errorf("DPoP proof typ must be \"dpop+jwt\", got %q", header.Typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// alg MUST identify an asymmetric digital signature algorithm;
|
||||||
|
// symmetric algorithms (HS*) are explicitly forbidden (RFC 9449 §4.2).
|
||||||
|
if header.Alg == "" || strings.HasPrefix(header.Alg, "HS") {
|
||||||
|
return "", fmt.Errorf("DPoP proof must use an asymmetric algorithm, got %q", header.Alg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwk MUST be present (RFC 9449 §4.2).
|
||||||
|
if len(header.JWK) == 0 {
|
||||||
|
return "", fmt.Errorf("DPoP proof header must contain the jwk claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
var jwkKey jose.JSONWebKey
|
||||||
|
if err = jwkKey.UnmarshalJSON(header.JWK); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse DPoP JWK: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the JWK SHA-256 thumbprint per RFC 7638.
|
||||||
|
thumbprintBytes, err := jwkKey.Thumbprint(crypto.SHA256)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to compute DPoP JWK thumbprint: %w", err)
|
||||||
|
}
|
||||||
|
jkt := base64.RawURLEncoding.EncodeToString(thumbprintBytes)
|
||||||
|
|
||||||
|
// Verify the proof's signature using the public key embedded in the header.
|
||||||
|
// WithoutClaimsValidation is used so that we can perform all claim checks
|
||||||
|
// ourselves (jwt library exp/nbf validation is not appropriate here).
|
||||||
|
t, err := jwt.ParseWithClaims(proofToken, &DPoPProofClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return jwkKey.Key, nil
|
||||||
|
}, jwt.WithoutClaimsValidation())
|
||||||
|
if err != nil || !t.Valid {
|
||||||
|
return "", fmt.Errorf("DPoP proof signature verification failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := t.Claims.(*DPoPProofClaims)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("failed to parse DPoP proof claims")
|
||||||
|
}
|
||||||
|
|
||||||
|
// htm MUST match the HTTP request method (RFC 9449 §4.2).
|
||||||
|
if !strings.EqualFold(claims.Htm, method) {
|
||||||
|
return "", fmt.Errorf("DPoP proof htm %q does not match request method %q", claims.Htm, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// htu MUST match the request URL without query/fragment (RFC 9449 §4.2).
|
||||||
|
if !strings.EqualFold(claims.Htu, htu) {
|
||||||
|
return "", fmt.Errorf("DPoP proof htu %q does not match request URL %q", claims.Htu, htu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// iat MUST be present and within the acceptable time window (RFC 9449 §4.2).
|
||||||
|
if claims.IssuedAt == nil {
|
||||||
|
return "", fmt.Errorf("DPoP proof missing iat claim")
|
||||||
|
}
|
||||||
|
age := time.Since(claims.IssuedAt.Time).Abs()
|
||||||
|
if age > time.Duration(dpopMaxAgeSeconds)*time.Second {
|
||||||
|
return "", fmt.Errorf("DPoP proof iat is outside the acceptable time window (%d seconds)", dpopMaxAgeSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// jti MUST be present to support replay detection (RFC 9449 §4.2).
|
||||||
|
if claims.Jti == "" {
|
||||||
|
return "", fmt.Errorf("DPoP proof missing jti claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ath MUST be validated at protected resource endpoints (RFC 9449 §4.2).
|
||||||
|
// It is the base64url-encoded SHA-256 hash of the ASCII access token string.
|
||||||
|
if accessToken != "" {
|
||||||
|
hash := sha256.Sum256([]byte(accessToken))
|
||||||
|
expectedAth := base64.RawURLEncoding.EncodeToString(hash[:])
|
||||||
|
if claims.Ath != expectedAth {
|
||||||
|
return "", fmt.Errorf("DPoP proof ath claim does not match access token hash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jkt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDPoPHtu constructs the full DPoP htu URL for a given host and path.
|
||||||
|
// It uses the same origin-detection logic as the rest of the backend.
|
||||||
|
func GetDPoPHtu(host, path string) string {
|
||||||
|
_, originBackend := getOriginFromHost(host)
|
||||||
|
return originBackend + path
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, nonce string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string, subjectToken string, subjectTokenType string, assertion string, clientAssertion string, clientAssertionType string, audience string, resource string) (interface{}, error) {
|
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, nonce string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string, subjectToken string, subjectTokenType string, assertion string, clientAssertion string, clientAssertionType string, audience string, resource string, dpopProof string) (interface{}, error) {
|
||||||
var (
|
var (
|
||||||
application *Application
|
application *Application
|
||||||
err error
|
err error
|
||||||
@@ -85,7 +85,7 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
case "urn:ietf:params:oauth:grant-type:token-exchange": // Token Exchange Grant (RFC 8693)
|
case "urn:ietf:params:oauth:grant-type:token-exchange": // Token Exchange Grant (RFC 8693)
|
||||||
token, tokenError, err = GetTokenExchangeToken(application, clientSecret, subjectToken, subjectTokenType, audience, scope, host)
|
token, tokenError, err = GetTokenExchangeToken(application, clientSecret, subjectToken, subjectTokenType, audience, scope, host)
|
||||||
case "refresh_token":
|
case "refresh_token":
|
||||||
refreshToken2, err := RefreshToken(application, grantType, refreshToken, scope, clientId, clientSecret, host)
|
refreshToken2, err := RefreshToken(application, grantType, refreshToken, scope, clientId, clientSecret, host, dpopProof)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -108,6 +108,23 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
return tokenError, nil
|
return tokenError, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply DPoP binding (RFC 9449) if a DPoP proof was supplied by the client.
|
||||||
|
if dpopProof != "" {
|
||||||
|
dpopHtu := GetDPoPHtu(host, "/api/login/oauth/access_token")
|
||||||
|
jkt, dpopErr := ValidateDPoPProof(dpopProof, "POST", dpopHtu, "")
|
||||||
|
if dpopErr != nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: "invalid_dpop_proof",
|
||||||
|
ErrorDescription: dpopErr.Error(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
token.TokenType = "DPoP"
|
||||||
|
token.DPoPJkt = jkt
|
||||||
|
if err = updateTokenDPoP(token); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
token.CodeIsUsed = true
|
token.CodeIsUsed = true
|
||||||
|
|
||||||
_, err = updateUsedByCode(token)
|
_, err = updateUsedByCode(token)
|
||||||
|
|||||||
@@ -62,19 +62,25 @@ type TokenError struct {
|
|||||||
ErrorDescription string `json:"error_description,omitempty"`
|
ErrorDescription string `json:"error_description,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DPoPConfirmation holds the DPoP key confirmation claim (RFC 9449).
|
||||||
|
type DPoPConfirmation struct {
|
||||||
|
JKT string `json:"jkt"`
|
||||||
|
}
|
||||||
|
|
||||||
type IntrospectionResponse struct {
|
type IntrospectionResponse struct {
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Scope string `json:"scope,omitempty"`
|
Scope string `json:"scope,omitempty"`
|
||||||
ClientId string `json:"client_id,omitempty"`
|
ClientId string `json:"client_id,omitempty"`
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
TokenType string `json:"token_type,omitempty"`
|
TokenType string `json:"token_type,omitempty"`
|
||||||
Exp int64 `json:"exp,omitempty"`
|
Exp int64 `json:"exp,omitempty"`
|
||||||
Iat int64 `json:"iat,omitempty"`
|
Iat int64 `json:"iat,omitempty"`
|
||||||
Nbf int64 `json:"nbf,omitempty"`
|
Nbf int64 `json:"nbf,omitempty"`
|
||||||
Sub string `json:"sub,omitempty"`
|
Sub string `json:"sub,omitempty"`
|
||||||
Aud []string `json:"aud,omitempty"`
|
Aud []string `json:"aud,omitempty"`
|
||||||
Iss string `json:"iss,omitempty"`
|
Iss string `json:"iss,omitempty"`
|
||||||
Jti string `json:"jti,omitempty"`
|
Jti string `json:"jti,omitempty"`
|
||||||
|
Cnf *DPoPConfirmation `json:"cnf,omitempty"` // RFC 9449 DPoP key binding
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceAuthCache struct {
|
type DeviceAuthCache struct {
|
||||||
@@ -349,7 +355,7 @@ func GetOAuthCode(userId string, clientId string, provider string, signinMethod
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RefreshToken(application *Application, grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) (interface{}, error) {
|
func RefreshToken(application *Application, grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string, dpopProof string) (interface{}, error) {
|
||||||
if grantType != "refresh_token" {
|
if grantType != "refresh_token" {
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
Error: UnsupportedGrantType,
|
Error: UnsupportedGrantType,
|
||||||
@@ -480,6 +486,23 @@ func RefreshToken(application *Application, grantType string, refreshToken strin
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply DPoP binding to the refreshed token if a DPoP proof was provided.
|
||||||
|
if dpopProof != "" {
|
||||||
|
dpopHtu := GetDPoPHtu(host, "/api/login/oauth/access_token")
|
||||||
|
jkt, err := ValidateDPoPProof(dpopProof, "POST", dpopHtu, "")
|
||||||
|
if err != nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: "invalid_dpop_proof",
|
||||||
|
ErrorDescription: err.Error(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
newToken.TokenType = "DPoP"
|
||||||
|
newToken.DPoPJkt = jkt
|
||||||
|
if err = updateTokenDPoP(newToken); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_, err = DeleteToken(token)
|
_, err = DeleteToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ type OidcDiscovery struct {
|
|||||||
RequestParameterSupported bool `json:"request_parameter_supported"`
|
RequestParameterSupported bool `json:"request_parameter_supported"`
|
||||||
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
|
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
|
||||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||||
|
DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported,omitempty"` // RFC 9449
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebFinger struct {
|
type WebFinger struct {
|
||||||
@@ -167,6 +168,7 @@ func GetOidcDiscovery(host string, applicationName string) OidcDiscovery {
|
|||||||
RequestParameterSupported: true,
|
RequestParameterSupported: true,
|
||||||
RequestObjectSigningAlgValuesSupported: []string{"HS256", "HS384", "HS512"},
|
RequestObjectSigningAlgValuesSupported: []string{"HS256", "HS384", "HS512"},
|
||||||
EndSessionEndpoint: fmt.Sprintf("%s/api/logout", originBackend),
|
EndSessionEndpoint: fmt.Sprintf("%s/api/logout", originBackend),
|
||||||
|
DPoPSigningAlgValuesSupported: []string{"RS256", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512"},
|
||||||
}
|
}
|
||||||
|
|
||||||
return oidcDiscovery
|
return oidcDiscovery
|
||||||
|
|||||||
@@ -70,6 +70,25 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate DPoP proof for DPoP-bound tokens (RFC 9449).
|
||||||
|
if token.TokenType == "DPoP" {
|
||||||
|
dpopProof := ctx.Request.Header.Get("DPoP")
|
||||||
|
if dpopProof == "" {
|
||||||
|
responseError(ctx, "DPoP proof header required for DPoP-bound access token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
htu := object.GetDPoPHtu(ctx.Request.Host, ctx.Request.URL.Path)
|
||||||
|
jkt, dpopErr := object.ValidateDPoPProof(dpopProof, ctx.Request.Method, htu, accessToken)
|
||||||
|
if dpopErr != nil {
|
||||||
|
responseError(ctx, fmt.Sprintf("DPoP proof validation failed: %s", dpopErr.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if jkt != token.DPoPJkt {
|
||||||
|
responseError(ctx, "DPoP proof key binding mismatch")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userId := util.GetId(token.Organization, token.User)
|
userId := util.GetId(token.Organization, token.User)
|
||||||
application, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
application, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -238,8 +238,9 @@ func parseBearerToken(ctx *context.Context) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accept both "Bearer" (RFC 6750) and "DPoP" (RFC 9449) authorization schemes.
|
||||||
prefix := tokens[0]
|
prefix := tokens[0]
|
||||||
if prefix != "Bearer" {
|
if prefix != "Bearer" && prefix != "DPoP" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user