【问题标题】:How do I validate a Microsoft jwt id_token?如何验证 Microsoft jwt id_token?
【发布时间】:2020-03-27 19:29:19
【问题描述】:

我正在使用从 Microsoft 到客户端的 jwt 令牌来验证来自它的请求到 Web API(服务器)。我可以控制客户端 (js) 和服务器 (Python) 的代码。

在客户端,我使用以下请求来获取令牌(用户通过租户上的密码/2FA 声明):

`https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize
            ?response_type=id_token+token
            &client_id=${CLIENT_ID}
            &redirect_uri=${redirect_uri}
            &scope=openid+email+profile
            &state=${guid()}
            &nonce=${guid()}`

这里guid是唯一值,TENANT_ID是租户,CLIENT_ID是客户端。

得到这个令牌后,我将它作为授权标头发送,如下所示:

init = {
    headers: {
       'Authorization': `Bearer ${token}`,
    }
}

return fetch(url, init).then(response => {
    return response.json()
})

然后,我在服务器上检索令牌并验证它:

if 'Authorization' in request.headers and request.headers['Authorization'].startswith('Bearer '):
    token = request.headers['Authorization'][len('Bearer '):]
    from authlib.jose import jwt
    claims = jwt.decode(token, jwk)

其中jwkhttps://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys 的内容。

整个流程一直有效,直到验证失败,并出现以下错误:

authlib.jose.errors.InvalidHeaderParameterName: invalid_header_parameter_name: Invalid Header Parameter Names: nonce

这表示令牌的头部包含一个密钥nonce(我验证过)。

查看 Microsoft 的相关文档 here,标头上没有对 nonce 的引用——只是在有效负载上。

Q1:我在这里做错了什么?

Q2:假设 Microsoft 是将 nonce 放在错误位置的人(标头而不是有效负载),是否可以在将 nonce 传递给 jose 的身份验证库之前从标头(在服务器端)中删除它?这样做安全吗?

【问题讨论】:

    标签: jwt jose microsoft-identity-platform


    【解决方案1】:

    见:https://robertoprevato.github.io/Validating-JWT-Bearer-tokens-from-Azure-AD-in-Python/

    这是我在 API 中验证 Azure AD Id_tokens 的方法:

    import base64
    import jwt
    from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import serialization
    
    
    def ensure_bytes(key):
        if isinstance(key, str):
            key = key.encode('utf-8')
        return key
    
    
    def decode_value(val):
        decoded = base64.urlsafe_b64decode(ensure_bytes(val) + b'==')
        return int.from_bytes(decoded, 'big')
    
    
    def rsa_pem_from_jwk(jwk):
        return RSAPublicNumbers(
            n=decode_value(jwk['n']),
            e=decode_value(jwk['e'])
        ).public_key(default_backend()).public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
    
    
    # obtain jwks as you wish: configuration file, HTTP GET request to the endpoint returning them;
    jwks = {
        "keys": [
            {
                "kty": "RSA",
                "use": "sig",
                "kid": "piVlloQDSMKx...",
                "x5t": "piVlloQDSMKx...",
                "n": "0XhhwpmEpN-jDBapnzhF...",
                "e": "AQAB",
                "x5c": [
                    "MIIDBTCCAe2gAwIBAgIQMCJcg...."
                ],
                "issuer": "https://login.microsoftonline.com/{tenant}/v2.0"
            }
        ]
    }
    
    # configuration, these can be seen in valid JWTs from Azure B2C:
    valid_audiences = ['dd050a67-ebfd-xxx-xxxx-xxxxxxxx'] # id of the application prepared previously
    
    class InvalidAuthorizationToken(Exception):
        def __init__(self, details):
            super().__init__('Invalid authorization token: ' + details)
    
    
    def get_kid(token):
        headers = jwt.get_unverified_header(token)
        if not headers:
            raise InvalidAuthorizationToken('missing headers')
        try:
            return headers['kid']
        except KeyError:
            raise InvalidAuthorizationToken('missing kid')
    
    
    
    def get_jwk(kid):
        for jwk in jwks.get('keys'):
            if jwk.get('kid') == kid:
                return jwk
        raise InvalidAuthorizationToken('kid not recognized')
    
    def get_issuer(kid):
        for jwk in jwks.get('keys'):
            if jwk.get('kid') == kid:
                return jwk.get('issuer')
        raise InvalidAuthorizationToken('kid not recognized')
    
    def get_public_key(token):
        return rsa_pem_from_jwk(get_jwk(get_kid(token)))
    
    
    def validate_jwt(jwt_to_validate):
        try:
            public_key = get_public_key(jwt_to_validate)
        issuer = get_issuer(kid)
    
    
            options = {
                'verify_signature': True,
                'verify_exp': True,  # Skipping expiration date check
                'verify_nbf': False,
                'verify_iat': False,
                'verify_aud': True  # Skipping audience check
            }
    
            decoded = jwt.decode(jwt_to_validate,
                                 public_key,
                                 options=options,
                                 algorithms=['RS256'],
                                 audience=valid_audiences,
                                 issuer=issuer)
    
            # do what you wish with decoded token:
            # if we get here, the JWT is validated
            print(decoded)
    
        except Exception as ex:
            print('The JWT is not valid!')
            return False
        else:
            return decoded
            print('The JWT is valid!')
    
    

    【讨论】:

    • @chatzich 见docs.microsoft.com/en-us/azure/active-directory-b2c/… 格式:https://{tenant_name}.b2clogin.com/{tenant_name}.onmicrosoft.com/{policy_name}/discovery/v2.0/keys 例如:https://contoso.b2clogin.com/contoso.onmicrosoft.com/b2c_1_signupsignin1/discovery/v2.0/keys
    猜你喜欢
    • 2017-08-25
    • 2017-03-31
    • 1970-01-01
    • 2018-01-31
    • 1970-01-01
    • 2021-03-04
    • 2017-05-19
    • 1970-01-01
    • 2017-05-18
    相关资源
    最近更新 更多