blob: bf6bf2c7018136c29ef9cd5b33b251eeec51fa1c [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
36 if id_info['iss'] != 'https://accounts.google.com':
37 raise ValueError('Wrong issuer.')
38
39 userid = id_info['sub']
40
41By default, this will re-fetch certificates for each verification. Because
42Google's public keys are only changed infrequently (on the order of once per
43day), you may wish to take advantage of caching to reduce latency and the
44potential for network errors. This can be accomplished using an external
45library like `CacheControl`_ to create a cache-aware
46:class:`google.auth.transport.Request`::
47
48 import cachecontrol
49 import google.auth.transport.requests
50 import requests
51
52 session = requests.session()
53 cached_session = cachecontrol.CacheControl(session)
54 request = google.auth.transport.requests.Request(session=cached_session)
55
56.. _OpenID Connect ID Token:
57 http://openid.net/specs/openid-connect-core-1_0.html#IDToken
58.. _CacheControl: https://cachecontrol.readthedocs.io
59"""
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080060
61import json
arithmetic1728506c5652020-04-01 10:34:37 -070062import os
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080063
arithmetic1728506c5652020-04-01 10:34:37 -070064import six
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080065from six.moves import http_client
66
arithmetic1728506c5652020-04-01 10:34:37 -070067from google.auth import environment_vars
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080068from google.auth import exceptions
69from google.auth import jwt
70
arithmetic1728506c5652020-04-01 10:34:37 -070071
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080072# The URL that provides public certificates for verifying ID tokens issued
73# by Google's OAuth 2.0 authorization server.
k-yone901c2592020-02-15 02:44:04 +090074_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs"
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080075
76# The URL that provides public certificates for verifying ID tokens issued
77# by Firebase and the Google APIs infrastructure
78_GOOGLE_APIS_CERTS_URL = (
Bu Sun Kim9eec0912019-10-21 17:04:21 -070079 "https://www.googleapis.com/robot/v1/metadata/x509"
80 "/securetoken@system.gserviceaccount.com"
81)
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080082
Bu Sun Kimc05b8b52020-06-29 16:27:30 -070083_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"]
84
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080085
86def _fetch_certs(request, certs_url):
87 """Fetches certificates.
88
89 Google-style cerificate endpoints return JSON in the format of
90 ``{'key id': 'x509 certificate'}``.
91
92 Args:
93 request (google.auth.transport.Request): The object used to make
94 HTTP requests.
95 certs_url (str): The certificate endpoint URL.
96
97 Returns:
98 Mapping[str, str]: A mapping of public key ID to x.509 certificate
99 data.
100 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700101 response = request(certs_url, method="GET")
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800102
103 if response.status != http_client.OK:
104 raise exceptions.TransportError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700105 "Could not fetch certificates at {}".format(certs_url)
106 )
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800107
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700108 return json.loads(response.data.decode("utf-8"))
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800109
110
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700111def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERTS_URL):
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800112 """Verifies an ID token and returns the decoded token.
113
114 Args:
115 id_token (Union[str, bytes]): The encoded token.
116 request (google.auth.transport.Request): The object used to make
117 HTTP requests.
118 audience (str): The audience that this token is intended for. If None
119 then the audience is not verified.
120 certs_url (str): The URL that specifies the certificates to use to
121 verify the token. This URL should return JSON in the format of
122 ``{'key id': 'x509 certificate'}``.
123
124 Returns:
125 Mapping[str, Any]: The decoded token.
126 """
127 certs = _fetch_certs(request, certs_url)
128
129 return jwt.decode(id_token, certs=certs, audience=audience)
130
131
132def verify_oauth2_token(id_token, request, audience=None):
133 """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
134
135 Args:
136 id_token (Union[str, bytes]): The encoded token.
137 request (google.auth.transport.Request): The object used to make
138 HTTP requests.
139 audience (str): The audience that this token is intended for. This is
140 typically your application's OAuth 2.0 client ID. If None then the
141 audience is not verified.
142
143 Returns:
144 Mapping[str, Any]: The decoded token.
Bu Sun Kimc05b8b52020-06-29 16:27:30 -0700145
146 Raises:
147 exceptions.GoogleAuthError: If the issuer is invalid.
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800148 """
Bu Sun Kimc05b8b52020-06-29 16:27:30 -0700149 idinfo = verify_token(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700150 id_token, request, audience=audience, certs_url=_GOOGLE_OAUTH2_CERTS_URL
151 )
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800152
Bu Sun Kimc05b8b52020-06-29 16:27:30 -0700153 if idinfo["iss"] not in _GOOGLE_ISSUERS:
154 raise exceptions.GoogleAuthError(
155 "Wrong issuer. 'iss' should be one of the following: {}".format(
156 _GOOGLE_ISSUERS
157 )
158 )
159
160 return idinfo
161
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800162
163def verify_firebase_token(id_token, request, audience=None):
164 """Verifies an ID Token issued by Firebase Authentication.
165
166 Args:
167 id_token (Union[str, bytes]): The encoded token.
168 request (google.auth.transport.Request): The object used to make
169 HTTP requests.
170 audience (str): The audience that this token is intended for. This is
171 typically your Firebase application ID. If None then the audience
172 is not verified.
173
174 Returns:
175 Mapping[str, Any]: The decoded token.
176 """
177 return verify_token(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700178 id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL
179 )
arithmetic1728506c5652020-04-01 10:34:37 -0700180
181
182def fetch_id_token(request, audience):
183 """Fetch the ID Token from the current environment.
184
185 This function acquires ID token from the environment in the following order:
186
187 1. If the application is running in Compute Engine, App Engine or Cloud Run,
188 then the ID token are obtained from the metadata server.
189 2. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
190 to the path of a valid service account JSON file, then ID token is
191 acquired using this service account credentials.
192 3. If metadata server doesn't exist and no valid service account credentials
193 are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
194 be raised.
195
196 Example::
197
198 import google.oauth2.id_token
199 import google.auth.transport.requests
200
201 request = google.auth.transport.requests.Request()
202 target_audience = "https://pubsub.googleapis.com"
203
204 id_token = google.oauth2.id_token.fetch_id_token(request, target_audience)
205
206 Args:
207 request (google.auth.transport.Request): A callable used to make
208 HTTP requests.
209 audience (str): The audience that this ID token is intended for.
210
211 Returns:
212 str: The ID token.
213
214 Raises:
215 ~google.auth.exceptions.DefaultCredentialsError:
216 If metadata server doesn't exist and no valid service account
217 credentials are found.
218 """
219 # 1. First try to fetch ID token from metada server if it exists. The code
220 # works for GAE and Cloud Run metadata server as well.
221 try:
222 from google.auth import compute_engine
223
224 credentials = compute_engine.IDTokenCredentials(
225 request, audience, use_metadata_identity_endpoint=True
226 )
227 credentials.refresh(request)
228 return credentials.token
arithmetic17283d672e92020-05-11 14:22:32 -0700229 except (ImportError, exceptions.TransportError, exceptions.RefreshError):
arithmetic1728506c5652020-04-01 10:34:37 -0700230 pass
231
232 # 2. Try to use service account credentials to get ID token.
233
234 # Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
235 # variable.
236 credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
237 if not (
238 credentials_filename
239 and os.path.exists(credentials_filename)
240 and os.path.isfile(credentials_filename)
241 ):
242 raise exceptions.DefaultCredentialsError(
243 "Neither metadata server or valid service account credentials are found."
244 )
245
246 try:
247 with open(credentials_filename, "r") as f:
248 info = json.load(f)
249 credentials_content = (
250 (info.get("type") == "service_account") and info or None
251 )
252
253 from google.oauth2 import service_account
254
255 credentials = service_account.IDTokenCredentials.from_service_account_info(
256 credentials_content, target_audience=audience
257 )
258 except ValueError as caught_exc:
259 new_exc = exceptions.DefaultCredentialsError(
260 "Neither metadata server or valid service account credentials are found.",
261 caught_exc,
262 )
263 six.raise_from(new_exc, caught_exc)
264
265 credentials.refresh(request)
266 return credentials.token