-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fixes #199, adds built-in support for JWT Bearer authentication 🛡️
- Loading branch information
1 parent
3bf3049
commit 3dac548
Showing
11 changed files
with
346 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import logging | ||
from typing import Optional, Sequence | ||
|
||
from guardpost.asynchronous.authentication import AuthenticationHandler | ||
from guardpost.authentication import Identity, User | ||
from guardpost.jwks import KeysProvider | ||
from guardpost.jwts import InvalidAccessToken, JWTValidator | ||
from jwt.exceptions import InvalidTokenError | ||
|
||
from blacksheep.messages import Request | ||
|
||
|
||
def get_logger(): | ||
return logging.getLogger("blacksheep.server") | ||
|
||
|
||
class JWTBearerAuthentication(AuthenticationHandler): | ||
""" | ||
AuthenticationHandler that can parse and verify JWT Bearer access tokens to identify | ||
users. | ||
JWTs are validated using public RSA keys, and keys can be fetched automatically from | ||
OpenID Connect (OIDC) discovery, if an `authority` is provided. | ||
It is possible to use several instances of this class, to various authentication | ||
through several identity providers (e.g. Azure Active Directory, Auth0, Azure Active | ||
Directory B2C). | ||
""" | ||
|
||
def __init__( | ||
self, | ||
*, | ||
valid_issuers: Sequence[str], | ||
valid_audiences: Sequence[str], | ||
authority: Optional[str] = None, | ||
require_kid: bool = True, | ||
keys_provider: Optional[KeysProvider] = None, | ||
keys_url: Optional[str] = None, | ||
cache_time: float = 10800, | ||
auth_mode: str = "JWT Bearer" | ||
): | ||
""" | ||
Creates a new instance of JWTBearerAuthentication, which tries to | ||
obtains the identity of the user from the "Authorization" request header, | ||
handling JWT Bearer tokens. Only standard authorization headers starting | ||
with the `Bearer ` string are handled. | ||
Parameters | ||
---------- | ||
valid_issuers : Sequence[str] | ||
Sequence of acceptable issuers (iss). | ||
valid_audiences : Sequence[str] | ||
Sequence of acceptable audiences (aud). | ||
authority : Optional[str], optional | ||
If provided, keys are obtained from a standard well-known endpoint. | ||
This parameter is ignored if `keys_provider` is given. | ||
algorithms : Sequence[str], optional | ||
Sequence of acceptable algorithms, by default ["RS256"]. | ||
require_kid : bool, optional | ||
According to the specification, a key id is optional in JWK. However, | ||
this parameter lets control whether access tokens missing `kid` in their | ||
headers should be handled or rejected. By default True, thus only JWTs | ||
having `kid` header are accepted. | ||
keys_provider : Optional[KeysProvider], optional | ||
If provided, the exact `KeysProvider` to be used when fetching keys. | ||
By default None | ||
keys_url : Optional[str], optional | ||
If provided, keys are obtained from the given URL through HTTP GET. | ||
This parameter is ignored if `keys_provider` is given. | ||
cache_time : float, optional | ||
If >= 0, JWKS are cached in memory and stored for the given amount in | ||
seconds. By default 10800 (3 hours). | ||
auth_mode : str, optional | ||
When authentication succeeds, the declared authentication mode. By default, | ||
"JWT Bearer". | ||
""" | ||
self.logger = get_logger() | ||
|
||
self._validator = JWTValidator( | ||
authority=authority, | ||
require_kid=require_kid, | ||
keys_provider=keys_provider, | ||
keys_url=keys_url, | ||
valid_issuers=valid_issuers, | ||
valid_audiences=valid_audiences, | ||
cache_time=cache_time, | ||
) | ||
self.auth_mode = auth_mode | ||
self._validator.logger = self.logger | ||
|
||
async def authenticate(self, context: Request) -> Optional[Identity]: | ||
authorization_value = context.get_first_header(b"Authorization") | ||
|
||
if not authorization_value: | ||
context.identity = User({}) | ||
return None | ||
|
||
if not authorization_value.startswith(b"Bearer "): | ||
self.logger.debug( | ||
"Invalid Authorization header, not starting with `Bearer `, " | ||
"the header is ignored." | ||
) | ||
context.identity = User({}) | ||
return None | ||
|
||
token = authorization_value[7:].decode() | ||
|
||
try: | ||
decoded = await self._validator.validate_jwt(token) | ||
except (InvalidAccessToken, InvalidTokenError) as ex: | ||
# pass, because the application might support more than one | ||
# authentication method | ||
self.logger.error("JWT Bearer - invalid access token: %s", str(ex)) | ||
pass | ||
else: | ||
context.identity = User(decoded, self.auth_mode) | ||
return context.identity | ||
|
||
context.identity = User({}) | ||
return None |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
-----BEGIN PRIVATE KEY----- | ||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDHM7vHSAQxuS26 | ||
7lEtSqIAEk2q3iR2b/m5ywqBJ0dR1RxuQbbx0sCKSgVOSL1rCbJqlJs7zjOUoxof | ||
bihs57jiF3fax14w51CnRPGnET3kd9hUW4BZ18IzgHP/HfZKrnV7vHqhl3T284MU | ||
VvBOUwnbss8AS4jkbpRVArGEgH/ADYU4+Rsd4OdwHuNi/2QuJ2+k2Cnk8L5oocq/ | ||
1rLqCLsz7BRASl5AyOnIMU3an+rXYFZJRuxHA6OFi0K5KaYE2HTsyLPNUhCOmZlT | ||
l/qqZkfD76IlIEPnbCwjzuK1hsHBFwM3l98KiWQWkDy9rSEOVKEjapWbC1DctSll | ||
WK8q4k5LAgMBAAECggEATxXv8D9cQu11BWkGW4fs50BdC4BkU41DRQsiYYJZo1iL | ||
kA6Q9lMo0/5tOtZQNYXFCuFy+/xyqAlVHrNaY1pgIYsVr4tFjv7XG4GYuy5yNxmJ | ||
jnxBaenqFQ5jfx7DIIVA6V48BZme+0hUeyfFAiOfn1TPMBvM/nwUcee+2I83qORB | ||
u5uCnquDzhKEXYIriZ+4c6M+0oPYKeMrwty5vH7uwalDNvkGvKLoGoRounH0X2tV | ||
Yi5xzxUVC+KOopIk3fIWwdTSEJuQzoKo1gnZvt07f8sOh36dMCMONpxhM1GMX0Pg | ||
TgrJEg80UqDSxc3gYoAy0NpX4ttaWU4AziAIcf4XOQKBgQDtpHJg6Ya0WDsCuOnf | ||
IyGVPxoo6tTlLjP9ujpbt8ohzKTSnIc28DtKO4HQVqdNegJ0JsBV8VNAkhEh32/l | ||
i1Mde6F8uK4wt78yAslx256+t3Jjn59vcF3Tm8AaR8GB4CdG5PtIVKEUqXH4Rfkn | ||
87YHSmPL6VhN+JL04B4DSty/fQKBgQDWlxoIuj1Y2Xf1pAwpkn42YN10V1iY8/L5 | ||
hfIDLd6UC5G1hUxrePScOEWhZk9Ov2o2qAXBhchugx64H5CSTK9lbw1C0HWyL4v7 | ||
zup1omfqrjn7XXoX512LyTIYyV0oBWCm3V6kOJN3Ea7tALvVIQdfe4Z4fb9HSP/M | ||
FqGIOm+/ZwKBgQCi7VAN6Y2VL7ilkSmm9msb6/t/eiEkT50NpBRGtac7rRaD3xVF | ||
MUc1Cb9im0Zw8+miwL61LZMqffqJAquw8Oi3GgAJhoTGmfPX0dlS2oPntdYTP2kL | ||
+joZznrSice9x3SmQm+Vk5Asnk+pLDA6l/iA3xu0vfLw4i+++7kYAMd/8QKBgQCw | ||
34bD3s4l58mqnHax5V9GbvzZog0StTB2XuMln68wE4EcPyzIAMCN6wvphqyj2b4w | ||
Irnr0ttry4OMe+frzm1bi/dANRZtsicNfHVgVGaW1thPybKS9U7zovg52e+Axz3t | ||
C9WwQjm6EMc/7jTj7P9owiYKNotstEyy6Yxm/tOQzQKBgQCz1j1d3cLUEKY9IFJ1 | ||
Ew28dwwuL9pGFPHCXcDTqAAAKME9V3U8F7ZtYThE9u6pRfgXKLJWEY+qpH6FhuHQ | ||
DJdfiREM84kh/99Y6ZNjvHM7CK/5EZFTaEaqZGVlgR93Hkrs5LbKuucYffmlF69/ | ||
PDRlkWb0YatyOxBKznBgNJsz5A== | ||
-----END PRIVATE KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"keys": [ | ||
{ | ||
"kty": "RSA", | ||
"kid": "0", | ||
"n": "xzO7x0gEMbktuu5RLUqiABJNqt4kdm_5ucsKgSdHUdUcbkG28dLAikoFTki9awmyapSbO84zlKMaH24obOe44hd32sdeMOdQp0TxpxE95HfYVFuAWdfCM4Bz_x32Sq51e7x6oZd09vODFFbwTlMJ27LPAEuI5G6UVQKxhIB_wA2FOPkbHeDncB7jYv9kLidvpNgp5PC-aKHKv9ay6gi7M-wUQEpeQMjpyDFN2p_q12BWSUbsRwOjhYtCuSmmBNh07MizzVIQjpmZU5f6qmZHw--iJSBD52wsI87itYbBwRcDN5ffColkFpA8va0hDlShI2qVmwtQ3LUpZVivKuJOSw==", | ||
"e": "AQAB" | ||
} | ||
] | ||
} |
Oops, something went wrong.