id_token.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. # Copyright 2016 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Google ID Token helpers.
  15. Provides support for verifying `OpenID Connect ID Tokens`_, especially ones
  16. generated by Google infrastructure.
  17. To parse and verify an ID Token issued by Google's OAuth 2.0 authorization
  18. server use :func:`verify_oauth2_token`. To verify an ID Token issued by
  19. Firebase, use :func:`verify_firebase_token`.
  20. A general purpose ID Token verifier is available as :func:`verify_token`.
  21. Example::
  22. from google.oauth2 import id_token
  23. from google.auth.transport import requests
  24. request = requests.Request()
  25. id_info = id_token.verify_oauth2_token(
  26. token, request, 'my-client-id.example.com')
  27. userid = id_info['sub']
  28. By default, this will re-fetch certificates for each verification. Because
  29. Google's public keys are only changed infrequently (on the order of once per
  30. day), you may wish to take advantage of caching to reduce latency and the
  31. potential for network errors. This can be accomplished using an external
  32. library like `CacheControl`_ to create a cache-aware
  33. :class:`google.auth.transport.Request`::
  34. import cachecontrol
  35. import google.auth.transport.requests
  36. import requests
  37. session = requests.session()
  38. cached_session = cachecontrol.CacheControl(session)
  39. request = google.auth.transport.requests.Request(session=cached_session)
  40. .. _OpenID Connect ID Tokens:
  41. http://openid.net/specs/openid-connect-core-1_0.html#IDToken
  42. .. _CacheControl: https://cachecontrol.readthedocs.io
  43. """
  44. import http.client as http_client
  45. import json
  46. import os
  47. from google.auth import environment_vars
  48. from google.auth import exceptions
  49. from google.auth import jwt
  50. # The URL that provides public certificates for verifying ID tokens issued
  51. # by Google's OAuth 2.0 authorization server.
  52. _GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs"
  53. # The URL that provides public certificates for verifying ID tokens issued
  54. # by Firebase and the Google APIs infrastructure
  55. _GOOGLE_APIS_CERTS_URL = (
  56. "https://www.googleapis.com/robot/v1/metadata/x509"
  57. "/securetoken@system.gserviceaccount.com"
  58. )
  59. _GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"]
  60. def _fetch_certs(request, certs_url):
  61. """Fetches certificates.
  62. Google-style cerificate endpoints return JSON in the format of
  63. ``{'key id': 'x509 certificate'}`` or a certificate array according
  64. to the JWK spec (see https://tools.ietf.org/html/rfc7517).
  65. Args:
  66. request (google.auth.transport.Request): The object used to make
  67. HTTP requests.
  68. certs_url (str): The certificate endpoint URL.
  69. Returns:
  70. Mapping[str, str] | Mapping[str, list]: A mapping of public keys
  71. in x.509 or JWK spec.
  72. """
  73. response = request(certs_url, method="GET")
  74. if response.status != http_client.OK:
  75. raise exceptions.TransportError(
  76. "Could not fetch certificates at {}".format(certs_url)
  77. )
  78. return json.loads(response.data.decode("utf-8"))
  79. def verify_token(
  80. id_token,
  81. request,
  82. audience=None,
  83. certs_url=_GOOGLE_OAUTH2_CERTS_URL,
  84. clock_skew_in_seconds=0,
  85. ):
  86. """Verifies an ID token and returns the decoded token.
  87. Args:
  88. id_token (Union[str, bytes]): The encoded token.
  89. request (google.auth.transport.Request): The object used to make
  90. HTTP requests.
  91. audience (str or list): The audience or audiences that this token is
  92. intended for. If None then the audience is not verified.
  93. certs_url (str): The URL that specifies the certificates to use to
  94. verify the token. This URL should return JSON in the format of
  95. ``{'key id': 'x509 certificate'}`` or a certificate array according to
  96. the JWK spec (see https://tools.ietf.org/html/rfc7517).
  97. clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
  98. validation.
  99. Returns:
  100. Mapping[str, Any]: The decoded token.
  101. """
  102. certs = _fetch_certs(request, certs_url)
  103. if "keys" in certs:
  104. try:
  105. import jwt as jwt_lib # type: ignore
  106. except ImportError as caught_exc: # pragma: NO COVER
  107. raise ImportError(
  108. "The pyjwt library is not installed, please install the pyjwt package to use the jwk certs format."
  109. ) from caught_exc
  110. jwks_client = jwt_lib.PyJWKClient(certs_url)
  111. signing_key = jwks_client.get_signing_key_from_jwt(id_token)
  112. return jwt_lib.decode(
  113. id_token,
  114. signing_key.key,
  115. algorithms=[signing_key.algorithm_name],
  116. audience=audience,
  117. )
  118. else:
  119. return jwt.decode(
  120. id_token,
  121. certs=certs,
  122. audience=audience,
  123. clock_skew_in_seconds=clock_skew_in_seconds,
  124. )
  125. def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0):
  126. """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
  127. Args:
  128. id_token (Union[str, bytes]): The encoded token.
  129. request (google.auth.transport.Request): The object used to make
  130. HTTP requests.
  131. audience (str): The audience that this token is intended for. This is
  132. typically your application's OAuth 2.0 client ID. If None then the
  133. audience is not verified.
  134. clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
  135. validation.
  136. Returns:
  137. Mapping[str, Any]: The decoded token.
  138. Raises:
  139. exceptions.GoogleAuthError: If the issuer is invalid.
  140. ValueError: If token verification fails
  141. """
  142. idinfo = verify_token(
  143. id_token,
  144. request,
  145. audience=audience,
  146. certs_url=_GOOGLE_OAUTH2_CERTS_URL,
  147. clock_skew_in_seconds=clock_skew_in_seconds,
  148. )
  149. if idinfo["iss"] not in _GOOGLE_ISSUERS:
  150. raise exceptions.GoogleAuthError(
  151. "Wrong issuer. 'iss' should be one of the following: {}".format(
  152. _GOOGLE_ISSUERS
  153. )
  154. )
  155. return idinfo
  156. def verify_firebase_token(id_token, request, audience=None, clock_skew_in_seconds=0):
  157. """Verifies an ID Token issued by Firebase Authentication.
  158. Args:
  159. id_token (Union[str, bytes]): The encoded token.
  160. request (google.auth.transport.Request): The object used to make
  161. HTTP requests.
  162. audience (str): The audience that this token is intended for. This is
  163. typically your Firebase application ID. If None then the audience
  164. is not verified.
  165. clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
  166. validation.
  167. Returns:
  168. Mapping[str, Any]: The decoded token.
  169. """
  170. return verify_token(
  171. id_token,
  172. request,
  173. audience=audience,
  174. certs_url=_GOOGLE_APIS_CERTS_URL,
  175. clock_skew_in_seconds=clock_skew_in_seconds,
  176. )
  177. def fetch_id_token_credentials(audience, request=None):
  178. """Create the ID Token credentials from the current environment.
  179. This function acquires ID token from the environment in the following order.
  180. See https://google.aip.dev/auth/4110.
  181. 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
  182. to the path of a valid service account JSON file, then ID token is
  183. acquired using this service account credentials.
  184. 2. If the application is running in Compute Engine, App Engine or Cloud Run,
  185. then the ID token are obtained from the metadata server.
  186. 3. If metadata server doesn't exist and no valid service account credentials
  187. are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
  188. be raised.
  189. Example::
  190. import google.oauth2.id_token
  191. import google.auth.transport.requests
  192. request = google.auth.transport.requests.Request()
  193. target_audience = "https://pubsub.googleapis.com"
  194. # Create ID token credentials.
  195. credentials = google.oauth2.id_token.fetch_id_token_credentials(target_audience, request=request)
  196. # Refresh the credential to obtain an ID token.
  197. credentials.refresh(request)
  198. id_token = credentials.token
  199. id_token_expiry = credentials.expiry
  200. Args:
  201. audience (str): The audience that this ID token is intended for.
  202. request (Optional[google.auth.transport.Request]): A callable used to make
  203. HTTP requests. A request object will be created if not provided.
  204. Returns:
  205. google.auth.credentials.Credentials: The ID token credentials.
  206. Raises:
  207. ~google.auth.exceptions.DefaultCredentialsError:
  208. If metadata server doesn't exist and no valid service account
  209. credentials are found.
  210. """
  211. # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
  212. # variable.
  213. credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
  214. if credentials_filename:
  215. if not (
  216. os.path.exists(credentials_filename)
  217. and os.path.isfile(credentials_filename)
  218. ):
  219. raise exceptions.DefaultCredentialsError(
  220. "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid."
  221. )
  222. try:
  223. with open(credentials_filename, "r") as f:
  224. from google.oauth2 import service_account
  225. info = json.load(f)
  226. if info.get("type") == "service_account":
  227. return service_account.IDTokenCredentials.from_service_account_info(
  228. info, target_audience=audience
  229. )
  230. except ValueError as caught_exc:
  231. new_exc = exceptions.DefaultCredentialsError(
  232. "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.",
  233. caught_exc,
  234. )
  235. raise new_exc from caught_exc
  236. # 2. Try to fetch ID token from metada server if it exists. The code
  237. # works for GAE and Cloud Run metadata server as well.
  238. try:
  239. from google.auth import compute_engine
  240. from google.auth.compute_engine import _metadata
  241. # Create a request object if not provided.
  242. if not request:
  243. import google.auth.transport.requests
  244. request = google.auth.transport.requests.Request()
  245. if _metadata.ping(request):
  246. return compute_engine.IDTokenCredentials(
  247. request, audience, use_metadata_identity_endpoint=True
  248. )
  249. except (ImportError, exceptions.TransportError):
  250. pass
  251. raise exceptions.DefaultCredentialsError(
  252. "Neither metadata server or valid service account credentials are found."
  253. )
  254. def fetch_id_token(request, audience):
  255. """Fetch the ID Token from the current environment.
  256. This function acquires ID token from the environment in the following order.
  257. See https://google.aip.dev/auth/4110.
  258. 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
  259. to the path of a valid service account JSON file, then ID token is
  260. acquired using this service account credentials.
  261. 2. If the application is running in Compute Engine, App Engine or Cloud Run,
  262. then the ID token are obtained from the metadata server.
  263. 3. If metadata server doesn't exist and no valid service account credentials
  264. are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
  265. be raised.
  266. Example::
  267. import google.oauth2.id_token
  268. import google.auth.transport.requests
  269. request = google.auth.transport.requests.Request()
  270. target_audience = "https://pubsub.googleapis.com"
  271. id_token = google.oauth2.id_token.fetch_id_token(request, target_audience)
  272. Args:
  273. request (google.auth.transport.Request): A callable used to make
  274. HTTP requests.
  275. audience (str): The audience that this ID token is intended for.
  276. Returns:
  277. str: The ID token.
  278. Raises:
  279. ~google.auth.exceptions.DefaultCredentialsError:
  280. If metadata server doesn't exist and no valid service account
  281. credentials are found.
  282. """
  283. id_token_credentials = fetch_id_token_credentials(audience, request=request)
  284. id_token_credentials.refresh(request)
  285. return id_token_credentials.token