blob: 5fbb6a13371c777f568027f6a2aaf66b42538166 [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
arithmetic1728506c5652020-04-01 10:34:37 -070061import six
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080062from 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
67
arithmetic1728506c5652020-04-01 10:34:37 -070068
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080069# The URL that provides public certificates for verifying ID tokens issued
70# by Google's OAuth 2.0 authorization server.
k-yone901c2592020-02-15 02:44:04 +090071_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs"
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080072
73# The URL that provides public certificates for verifying ID tokens issued
74# by Firebase and the Google APIs infrastructure
75_GOOGLE_APIS_CERTS_URL = (
Bu Sun Kim9eec0912019-10-21 17:04:21 -070076 "https://www.googleapis.com/robot/v1/metadata/x509"
77 "/securetoken@system.gserviceaccount.com"
78)
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080079
Bu Sun Kimc05b8b52020-06-29 16:27:30 -070080_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"]
81
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080082
83def _fetch_certs(request, certs_url):
84 """Fetches certificates.
85
86 Google-style cerificate endpoints return JSON in the format of
87 ``{'key id': 'x509 certificate'}``.
88
89 Args:
90 request (google.auth.transport.Request): The object used to make
91 HTTP requests.
92 certs_url (str): The certificate endpoint URL.
93
94 Returns:
95 Mapping[str, str]: A mapping of public key ID to x.509 certificate
96 data.
97 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -070098 response = request(certs_url, method="GET")
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -080099
100 if response.status != http_client.OK:
101 raise exceptions.TransportError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700102 "Could not fetch certificates at {}".format(certs_url)
103 )
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800104
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700105 return json.loads(response.data.decode("utf-8"))
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800106
107
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700108def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERTS_URL):
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800109 """Verifies an ID token and returns the decoded token.
110
111 Args:
112 id_token (Union[str, bytes]): The encoded token.
113 request (google.auth.transport.Request): The object used to make
114 HTTP requests.
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400115 audience (str or list): The audience or audiences that this token is
116 intended for. If None then the audience is not verified.
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800117 certs_url (str): The URL that specifies the certificates to use to
118 verify the token. This URL should return JSON in the format of
119 ``{'key id': 'x509 certificate'}``.
120
121 Returns:
122 Mapping[str, Any]: The decoded token.
123 """
124 certs = _fetch_certs(request, certs_url)
125
126 return jwt.decode(id_token, certs=certs, audience=audience)
127
128
129def verify_oauth2_token(id_token, request, audience=None):
130 """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
131
132 Args:
133 id_token (Union[str, bytes]): The encoded token.
134 request (google.auth.transport.Request): The object used to make
135 HTTP requests.
136 audience (str): The audience that this token is intended for. This is
137 typically your application's OAuth 2.0 client ID. If None then the
138 audience is not verified.
139
140 Returns:
141 Mapping[str, Any]: The decoded token.
Bu Sun Kimc05b8b52020-06-29 16:27:30 -0700142
143 Raises:
144 exceptions.GoogleAuthError: If the issuer is invalid.
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800145 """
Bu Sun Kimc05b8b52020-06-29 16:27:30 -0700146 idinfo = verify_token(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700147 id_token, request, audience=audience, certs_url=_GOOGLE_OAUTH2_CERTS_URL
148 )
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800149
Bu Sun Kimc05b8b52020-06-29 16:27:30 -0700150 if idinfo["iss"] not in _GOOGLE_ISSUERS:
151 raise exceptions.GoogleAuthError(
152 "Wrong issuer. 'iss' should be one of the following: {}".format(
153 _GOOGLE_ISSUERS
154 )
155 )
156
157 return idinfo
158
Jon Wayne Parrotte2ab1002016-11-10 15:11:48 -0800159
160def verify_firebase_token(id_token, request, audience=None):
161 """Verifies an ID Token issued by Firebase Authentication.
162
163 Args:
164 id_token (Union[str, bytes]): The encoded token.
165 request (google.auth.transport.Request): The object used to make
166 HTTP requests.
167 audience (str): The audience that this token is intended for. This is
168 typically your Firebase application ID. If None then the audience
169 is not verified.
170
171 Returns:
172 Mapping[str, Any]: The decoded token.
173 """
174 return verify_token(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700175 id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL
176 )
arithmetic1728506c5652020-04-01 10:34:37 -0700177
178
179def fetch_id_token(request, audience):
180 """Fetch the ID Token from the current environment.
181
182 This function acquires ID token from the environment in the following order:
183
184 1. 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 2. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
187 to the path of a valid service account JSON file, then ID token is
188 acquired using this service account credentials.
189 3. If metadata server doesn't exist and no valid service account credentials
190 are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
191 be raised.
192
193 Example::
194
195 import google.oauth2.id_token
196 import google.auth.transport.requests
197
198 request = google.auth.transport.requests.Request()
199 target_audience = "https://pubsub.googleapis.com"
200
201 id_token = google.oauth2.id_token.fetch_id_token(request, target_audience)
202
203 Args:
204 request (google.auth.transport.Request): A callable used to make
205 HTTP requests.
206 audience (str): The audience that this ID token is intended for.
207
208 Returns:
209 str: The ID token.
210
211 Raises:
212 ~google.auth.exceptions.DefaultCredentialsError:
213 If metadata server doesn't exist and no valid service account
214 credentials are found.
215 """
216 # 1. First try to fetch ID token from metada server if it exists. The code
217 # works for GAE and Cloud Run metadata server as well.
218 try:
219 from google.auth import compute_engine
220
221 credentials = compute_engine.IDTokenCredentials(
222 request, audience, use_metadata_identity_endpoint=True
223 )
224 credentials.refresh(request)
225 return credentials.token
arithmetic17283d672e92020-05-11 14:22:32 -0700226 except (ImportError, exceptions.TransportError, exceptions.RefreshError):
arithmetic1728506c5652020-04-01 10:34:37 -0700227 pass
228
229 # 2. Try to use service account credentials to get ID token.
230
231 # Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
232 # variable.
233 credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
234 if not (
235 credentials_filename
236 and os.path.exists(credentials_filename)
237 and os.path.isfile(credentials_filename)
238 ):
239 raise exceptions.DefaultCredentialsError(
240 "Neither metadata server or valid service account credentials are found."
241 )
242
243 try:
244 with open(credentials_filename, "r") as f:
245 info = json.load(f)
246 credentials_content = (
247 (info.get("type") == "service_account") and info or None
248 )
249
250 from google.oauth2 import service_account
251
252 credentials = service_account.IDTokenCredentials.from_service_account_info(
253 credentials_content, target_audience=audience
254 )
255 except ValueError as caught_exc:
256 new_exc = exceptions.DefaultCredentialsError(
257 "Neither metadata server or valid service account credentials are found.",
258 caught_exc,
259 )
260 six.raise_from(new_exc, caught_exc)
261
262 credentials.refresh(request)
263 return credentials.token