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 15"""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 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 53.. _OpenID Connect ID Tokens: 54 http://openid.net/specs/openid-connect-core-1_0.html#IDToken 55.. _CacheControl: https://cachecontrol.readthedocs.io 56""" 57 58import json 59import os 60 61import six 62from six.moves import http_client 63 64from google.auth import environment_vars 65from google.auth import exceptions 66from google.auth import jwt 67import google.auth.transport.requests 68 69 70# The URL that provides public certificates for verifying ID tokens issued 71# by Google's OAuth 2.0 authorization server. 72_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs" 73 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 = ( 77 "https://www.googleapis.com/robot/v1/metadata/x509" 78 "/[email protected]" 79) 80 81_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"] 82 83 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 """ 99 response = request(certs_url, method="GET") 100 101 if response.status != http_client.OK: 102 raise exceptions.TransportError( 103 "Could not fetch certificates at {}".format(certs_url) 104 ) 105 106 return json.loads(response.data.decode("utf-8")) 107 108 109def verify_token( 110 id_token, 111 request, 112 audience=None, 113 certs_url=_GOOGLE_OAUTH2_CERTS_URL, 114 clock_skew_in_seconds=0, 115): 116 """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. 122 audience (str or list): The audience or audiences that this token is 123 intended for. If None then the audience is not verified. 124 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'}``. 127 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` 128 validation. 129 130 Returns: 131 Mapping[str, Any]: The decoded token. 132 """ 133 certs = _fetch_certs(request, certs_url) 134 135 return jwt.decode( 136 id_token, 137 certs=certs, 138 audience=audience, 139 clock_skew_in_seconds=clock_skew_in_seconds, 140 ) 141 142 143def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0): 144 """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. 153 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` 154 validation. 155 156 Returns: 157 Mapping[str, Any]: The decoded token. 158 159 Raises: 160 exceptions.GoogleAuthError: If the issuer is invalid. 161 """ 162 idinfo = verify_token( 163 id_token, 164 request, 165 audience=audience, 166 certs_url=_GOOGLE_OAUTH2_CERTS_URL, 167 clock_skew_in_seconds=clock_skew_in_seconds, 168 ) 169 170 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 179 180def verify_firebase_token(id_token, request, audience=None, clock_skew_in_seconds=0): 181 """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. 190 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` 191 validation. 192 193 Returns: 194 Mapping[str, Any]: The decoded token. 195 """ 196 return verify_token( 197 id_token, 198 request, 199 audience=audience, 200 certs_url=_GOOGLE_APIS_CERTS_URL, 201 clock_skew_in_seconds=clock_skew_in_seconds, 202 ) 203 204 205def 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 300def fetch_id_token(request, audience): 301 """Fetch the ID Token from the current environment. 302 303 This function acquires ID token from the environment in the following order. 304 See https://google.aip.dev/auth/4110. 305 306 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set 307 to the path of a valid service account JSON file, then ID token is 308 acquired using this service account credentials. 309 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. 311 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 """ 338 id_token_credentials = fetch_id_token_credentials(audience, request=request) 339 id_token_credentials.refresh(request) 340 return id_token_credentials.token 341