blob: 74899ae5538299144efa5e73b20a632503ebac60 [file] [log] [blame]
C.J. Collier37141e42020-02-13 13:49:49 -08001# Copyright 2016 Google LLC
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -08002#
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
Arlan Jaska4255b102018-02-08 15:51:26 -080015"""Google ID Token helpers.
16
17Provides support for verifying `OpenID Connect ID Tokens`_, especially ones
18generated by Google infrastructure.
19
20To parse and verify an ID Token issued by Google's OAuth 2.0 authorization
21server use :func:`verify_oauth2_token`. To verify an ID Token issued by
22Firebase, use :func:`verify_firebase_token`.
23
24A general purpose ID Token verifier is available as :func:`verify_token`.
25
26Example::
27
28 from google.oauth2 import id_token
29 from google.auth.transport import requests
30
31 request = requests.Request()
32
33 id_info = id_token.verify_oauth2_token(
34 token, request, 'my-client-id.example.com')
35
Arlan Jaska4255b102018-02-08 15:51:26 -080036 userid = id_info['sub']
37
38By default, this will re-fetch certificates for each verification. Because
39Google's public keys are only changed infrequently (on the order of once per
40day), you may wish to take advantage of caching to reduce latency and the
41potential for network errors. This can be accomplished using an external
42library like `CacheControl`_ to create a cache-aware
43:class:`google.auth.transport.Request`::
44
45 import cachecontrol
46 import google.auth.transport.requests
47 import requests
48
49 session = requests.session()
50 cached_session = cachecontrol.CacheControl(session)
51 request = google.auth.transport.requests.Request(session=cached_session)
52
Bu Sun Kimb1a12d22021-02-26 11:50:02 -070053.. _OpenID Connect ID Tokens:
Arlan Jaska4255b102018-02-08 15:51:26 -080054 http://openid.net/specs/openid-connect-core-1_0.html#IDToken
55.. _CacheControl: https://cachecontrol.readthedocs.io
56"""
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080057
58import json
arithmetic1728506c5652020-04-01 10:34:37 -070059import os
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080060
arithmetic17285bd5ccf2021-10-21 15:25:46 -070061import six
62from six.moves import http_client
63
arithmetic1728506c5652020-04-01 10:34:37 -070064from google.auth import environment_vars
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080065from google.auth import exceptions
66from google.auth import jwt
arithmetic17288f1e9cf2021-10-29 11:59:01 -070067import google.auth.transport.requests
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080068
arithmetic1728506c5652020-04-01 10:34:37 -070069
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080070# The URL that provides public certificates for verifying ID tokens issued
71# by Google's OAuth 2.0 authorization server.
k-yone901c2592020-02-15 02:44:04 +090072_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs"
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080073
74# The URL that provides public certificates for verifying ID tokens issued
75# by Firebase and the Google APIs infrastructure
76_GOOGLE_APIS_CERTS_URL = (
Bu Sun Kim9eec0912019-10-21 17:04:21 -070077 "https://www.googleapis.com/robot/v1/metadata/x509"
78 "/securetoken@system.gserviceaccount.com"
79)
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080080
Bu Sun Kimc05b8b52020-06-29 16:27:30 -070081_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"]
82
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080083
84def _fetch_certs(request, certs_url):
85 """Fetches certificates.
86
87 Google-style cerificate endpoints return JSON in the format of
88 ``{'key id': 'x509 certificate'}``.
89
90 Args:
91 request (google.auth.transport.Request): The object used to make
92 HTTP requests.
93 certs_url (str): The certificate endpoint URL.
94
95 Returns:
96 Mapping[str, str]: A mapping of public key ID to x.509 certificate
97 data.
98 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -070099 response = request(certs_url, method="GET")
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800100
arithmetic17285bd5ccf2021-10-21 15:25:46 -0700101 if response.status != http_client.OK:
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800102 raise exceptions.TransportError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700103 "Could not fetch certificates at {}".format(certs_url)
104 )
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800105
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700106 return json.loads(response.data.decode("utf-8"))
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800107
108
arithmetic17288e95c1e2021-10-25 16:31:47 -0700109def verify_token(
110 id_token,
111 request,
112 audience=None,
113 certs_url=_GOOGLE_OAUTH2_CERTS_URL,
114 clock_skew_in_seconds=0,
115):
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800116 """Verifies an ID token and returns the decoded token.
117
118 Args:
119 id_token (Union[str, bytes]): The encoded token.
120 request (google.auth.transport.Request): The object used to make
121 HTTP requests.
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400122 audience (str or list): The audience or audiences that this token is
123 intended for. If None then the audience is not verified.
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800124 certs_url (str): The URL that specifies the certificates to use to
125 verify the token. This URL should return JSON in the format of
126 ``{'key id': 'x509 certificate'}``.
arithmetic17288e95c1e2021-10-25 16:31:47 -0700127 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
128 validation.
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800129
130 Returns:
131 Mapping[str, Any]: The decoded token.
132 """
133 certs = _fetch_certs(request, certs_url)
134
arithmetic17288e95c1e2021-10-25 16:31:47 -0700135 return jwt.decode(
136 id_token,
137 certs=certs,
138 audience=audience,
139 clock_skew_in_seconds=clock_skew_in_seconds,
140 )
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800141
142
arithmetic17288e95c1e2021-10-25 16:31:47 -0700143def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0):
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800144 """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
145
146 Args:
147 id_token (Union[str, bytes]): The encoded token.
148 request (google.auth.transport.Request): The object used to make
149 HTTP requests.
150 audience (str): The audience that this token is intended for. This is
151 typically your application's OAuth 2.0 client ID. If None then the
152 audience is not verified.
arithmetic17288e95c1e2021-10-25 16:31:47 -0700153 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
154 validation.
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800155
156 Returns:
157 Mapping[str, Any]: The decoded token.
Bu Sun Kimc05b8b52020-06-29 16:27:30 -0700158
159 Raises:
160 exceptions.GoogleAuthError: If the issuer is invalid.
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800161 """
Bu Sun Kimc05b8b52020-06-29 16:27:30 -0700162 idinfo = verify_token(
arithmetic17288e95c1e2021-10-25 16:31:47 -0700163 id_token,
164 request,
165 audience=audience,
166 certs_url=_GOOGLE_OAUTH2_CERTS_URL,
167 clock_skew_in_seconds=clock_skew_in_seconds,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700168 )
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800169
Bu Sun Kimc05b8b52020-06-29 16:27:30 -0700170 if idinfo["iss"] not in _GOOGLE_ISSUERS:
171 raise exceptions.GoogleAuthError(
172 "Wrong issuer. 'iss' should be one of the following: {}".format(
173 _GOOGLE_ISSUERS
174 )
175 )
176
177 return idinfo
178
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800179
arithmetic17288e95c1e2021-10-25 16:31:47 -0700180def verify_firebase_token(id_token, request, audience=None, clock_skew_in_seconds=0):
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800181 """Verifies an ID Token issued by Firebase Authentication.
182
183 Args:
184 id_token (Union[str, bytes]): The encoded token.
185 request (google.auth.transport.Request): The object used to make
186 HTTP requests.
187 audience (str): The audience that this token is intended for. This is
188 typically your Firebase application ID. If None then the audience
189 is not verified.
arithmetic17288e95c1e2021-10-25 16:31:47 -0700190 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
191 validation.
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800192
193 Returns:
194 Mapping[str, Any]: The decoded token.
195 """
196 return verify_token(
arithmetic17288e95c1e2021-10-25 16:31:47 -0700197 id_token,
198 request,
199 audience=audience,
200 certs_url=_GOOGLE_APIS_CERTS_URL,
201 clock_skew_in_seconds=clock_skew_in_seconds,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700202 )
arithmetic1728506c5652020-04-01 10:34:37 -0700203
204
arithmetic17288f1e9cf2021-10-29 11:59:01 -0700205def fetch_id_token_credentials(audience, request=None):
206 """Create the ID Token credentials from the current environment.
207
208 This function acquires ID token from the environment in the following order.
209 See https://google.aip.dev/auth/4110.
210
211 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
212 to the path of a valid service account JSON file, then ID token is
213 acquired using this service account credentials.
214 2. If the application is running in Compute Engine, App Engine or Cloud Run,
215 then the ID token are obtained from the metadata server.
216 3. If metadata server doesn't exist and no valid service account credentials
217 are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
218 be raised.
219
220 Example::
221
222 import google.oauth2.id_token
223 import google.auth.transport.requests
224
225 request = google.auth.transport.requests.Request()
226 target_audience = "https://pubsub.googleapis.com"
227
228 # Create ID token credentials.
229 credentials = google.oauth2.id_token.fetch_id_token_credentials(target_audience, request=request)
230
231 # Refresh the credential to obtain an ID token.
232 credentials.refresh(request)
233
234 id_token = credentials.token
235 id_token_expiry = credentials.expiry
236
237 Args:
238 audience (str): The audience that this ID token is intended for.
239 request (Optional[google.auth.transport.Request]): A callable used to make
240 HTTP requests. A request object will be created if not provided.
241
242 Returns:
243 google.auth.credentials.Credentials: The ID token credentials.
244
245 Raises:
246 ~google.auth.exceptions.DefaultCredentialsError:
247 If metadata server doesn't exist and no valid service account
248 credentials are found.
249 """
250 # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
251 # variable.
252 credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
253 if credentials_filename:
254 if not (
255 os.path.exists(credentials_filename)
256 and os.path.isfile(credentials_filename)
257 ):
258 raise exceptions.DefaultCredentialsError(
259 "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid."
260 )
261
262 try:
263 with open(credentials_filename, "r") as f:
264 from google.oauth2 import service_account
265
266 info = json.load(f)
267 if info.get("type") == "service_account":
268 return service_account.IDTokenCredentials.from_service_account_info(
269 info, target_audience=audience
270 )
271 except ValueError as caught_exc:
272 new_exc = exceptions.DefaultCredentialsError(
273 "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.",
274 caught_exc,
275 )
276 six.raise_from(new_exc, caught_exc)
277
278 # 2. Try to fetch ID token from metada server if it exists. The code
279 # works for GAE and Cloud Run metadata server as well.
280 try:
281 from google.auth import compute_engine
282 from google.auth.compute_engine import _metadata
283
284 # Create a request object if not provided.
285 if not request:
286 request = google.auth.transport.requests.Request()
287
288 if _metadata.ping(request):
289 return compute_engine.IDTokenCredentials(
290 request, audience, use_metadata_identity_endpoint=True
291 )
292 except (ImportError, exceptions.TransportError):
293 pass
294
295 raise exceptions.DefaultCredentialsError(
296 "Neither metadata server or valid service account credentials are found."
297 )
298
299
arithmetic1728506c5652020-04-01 10:34:37 -0700300def fetch_id_token(request, audience):
301 """Fetch the ID Token from the current environment.
302
arithmetic1728c34452e2021-07-14 11:40:05 -0700303 This function acquires ID token from the environment in the following order.
304 See https://google.aip.dev/auth/4110.
arithmetic1728506c5652020-04-01 10:34:37 -0700305
arithmetic1728c34452e2021-07-14 11:40:05 -0700306 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
arithmetic1728506c5652020-04-01 10:34:37 -0700307 to the path of a valid service account JSON file, then ID token is
308 acquired using this service account credentials.
arithmetic1728c34452e2021-07-14 11:40:05 -0700309 2. If the application is running in Compute Engine, App Engine or Cloud Run,
310 then the ID token are obtained from the metadata server.
arithmetic1728506c5652020-04-01 10:34:37 -0700311 3. If metadata server doesn't exist and no valid service account credentials
312 are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
313 be raised.
314
315 Example::
316
317 import google.oauth2.id_token
318 import google.auth.transport.requests
319
320 request = google.auth.transport.requests.Request()
321 target_audience = "https://pubsub.googleapis.com"
322
323 id_token = google.oauth2.id_token.fetch_id_token(request, target_audience)
324
325 Args:
326 request (google.auth.transport.Request): A callable used to make
327 HTTP requests.
328 audience (str): The audience that this ID token is intended for.
329
330 Returns:
331 str: The ID token.
332
333 Raises:
334 ~google.auth.exceptions.DefaultCredentialsError:
335 If metadata server doesn't exist and no valid service account
336 credentials are found.
337 """
arithmetic17288f1e9cf2021-10-29 11:59:01 -0700338 id_token_credentials = fetch_id_token_credentials(audience, request=request)
339 id_token_credentials.refresh(request)
340 return id_token_credentials.token