Skip to content

Commit e2ab100

Browse files
author
Jon Wayne Parrott
authored
Add ID token verification helpers. (#82)
1 parent bdbf2b1 commit e2ab100

File tree

4 files changed

+235
-0
lines changed

4 files changed

+235
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
google.oauth2.id_token module
2+
=============================
3+
4+
.. automodule:: google.oauth2.id_token
5+
:members:
6+
:inherited-members:
7+
:show-inheritance:

docs/reference/google.oauth2.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ Submodules
1212
.. toctree::
1313

1414
google.oauth2.credentials
15+
google.oauth2.id_token
1516
google.oauth2.service_account
1617

google/oauth2/id_token.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Copyright 2016 Google Inc.
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+
17+
import json
18+
19+
from six.moves import http_client
20+
21+
from google.auth import exceptions
22+
from google.auth import jwt
23+
24+
# The URL that provides public certificates for verifying ID tokens issued
25+
# by Google's OAuth 2.0 authorization server.
26+
_GOOGLE_OAUTH2_CERTS_URL = 'https://www.googleapis.com/oauth2/v1/certs'
27+
28+
# The URL that provides public certificates for verifying ID tokens issued
29+
# by Firebase and the Google APIs infrastructure
30+
_GOOGLE_APIS_CERTS_URL = (
31+
'https://www.googleapis.com/robot/v1/metadata/x509'
32+
33+
34+
35+
def _fetch_certs(request, certs_url):
36+
"""Fetches certificates.
37+
38+
Google-style cerificate endpoints return JSON in the format of
39+
``{'key id': 'x509 certificate'}``.
40+
41+
Args:
42+
request (google.auth.transport.Request): The object used to make
43+
HTTP requests.
44+
certs_url (str): The certificate endpoint URL.
45+
46+
Returns:
47+
Mapping[str, str]: A mapping of public key ID to x.509 certificate
48+
data.
49+
"""
50+
response = request('GET', certs_url)
51+
52+
if response.status != http_client.OK:
53+
raise exceptions.TransportError(
54+
'Could not fetch certificates at {}'.format(certs_url))
55+
56+
return json.loads(response.data.decode('utf-8'))
57+
58+
59+
def verify_token(id_token, request, audience=None,
60+
certs_url=_GOOGLE_OAUTH2_CERTS_URL):
61+
"""Verifies an ID token and returns the decoded token.
62+
63+
Args:
64+
id_token (Union[str, bytes]): The encoded token.
65+
request (google.auth.transport.Request): The object used to make
66+
HTTP requests.
67+
audience (str): The audience that this token is intended for. If None
68+
then the audience is not verified.
69+
certs_url (str): The URL that specifies the certificates to use to
70+
verify the token. This URL should return JSON in the format of
71+
``{'key id': 'x509 certificate'}``.
72+
73+
Returns:
74+
Mapping[str, Any]: The decoded token.
75+
"""
76+
certs = _fetch_certs(request, certs_url)
77+
78+
return jwt.decode(id_token, certs=certs, audience=audience)
79+
80+
81+
def verify_oauth2_token(id_token, request, audience=None):
82+
"""Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
83+
84+
Args:
85+
id_token (Union[str, bytes]): The encoded token.
86+
request (google.auth.transport.Request): The object used to make
87+
HTTP requests.
88+
audience (str): The audience that this token is intended for. This is
89+
typically your application's OAuth 2.0 client ID. If None then the
90+
audience is not verified.
91+
92+
Returns:
93+
Mapping[str, Any]: The decoded token.
94+
"""
95+
return verify_token(
96+
id_token, request, audience=audience,
97+
certs_url=_GOOGLE_OAUTH2_CERTS_URL)
98+
99+
100+
def verify_firebase_token(id_token, request, audience=None):
101+
"""Verifies an ID Token issued by Firebase Authentication.
102+
103+
Args:
104+
id_token (Union[str, bytes]): The encoded token.
105+
request (google.auth.transport.Request): The object used to make
106+
HTTP requests.
107+
audience (str): The audience that this token is intended for. This is
108+
typically your Firebase application ID. If None then the audience
109+
is not verified.
110+
111+
Returns:
112+
Mapping[str, Any]: The decoded token.
113+
"""
114+
return verify_token(
115+
id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL)

tests/oauth2/test_id_token.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Copyright 2014 Google Inc.
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+
import json
16+
17+
import mock
18+
import pytest
19+
20+
from google.auth import exceptions
21+
from google.oauth2 import id_token
22+
23+
24+
def make_request(status, data=None):
25+
response = mock.Mock()
26+
response.status = status
27+
28+
if data is not None:
29+
response.data = json.dumps(data).encode('utf-8')
30+
31+
return mock.Mock(return_value=response)
32+
33+
34+
def test__fetch_certs_success():
35+
certs = {'1': 'cert'}
36+
request = make_request(200, certs)
37+
38+
returned_certs = id_token._fetch_certs(request, mock.sentinel.cert_url)
39+
40+
request.assert_called_once_with('GET', mock.sentinel.cert_url)
41+
assert returned_certs == certs
42+
43+
44+
def test__fetch_certs_failure():
45+
request = make_request(404)
46+
47+
with pytest.raises(exceptions.TransportError):
48+
id_token._fetch_certs(request, mock.sentinel.cert_url)
49+
50+
request.assert_called_once_with('GET', mock.sentinel.cert_url)
51+
52+
53+
@mock.patch('google.auth.jwt.decode', autospec=True)
54+
@mock.patch('google.oauth2.id_token._fetch_certs', autospec=True)
55+
def test_verify_token(_fetch_certs, decode):
56+
result = id_token.verify_token(mock.sentinel.token, mock.sentinel.request)
57+
58+
assert result == decode.return_value
59+
_fetch_certs.assert_called_once_with(
60+
mock.sentinel.request, id_token._GOOGLE_OAUTH2_CERTS_URL)
61+
decode.assert_called_once_with(
62+
mock.sentinel.token,
63+
certs=_fetch_certs.return_value,
64+
audience=None)
65+
66+
67+
@mock.patch('google.auth.jwt.decode', autospec=True)
68+
@mock.patch('google.oauth2.id_token._fetch_certs', autospec=True)
69+
def test_verify_token_args(_fetch_certs, decode):
70+
result = id_token.verify_token(
71+
mock.sentinel.token,
72+
mock.sentinel.request,
73+
audience=mock.sentinel.audience,
74+
certs_url=mock.sentinel.certs_url)
75+
76+
assert result == decode.return_value
77+
_fetch_certs.assert_called_once_with(
78+
mock.sentinel.request, mock.sentinel.certs_url)
79+
decode.assert_called_once_with(
80+
mock.sentinel.token,
81+
certs=_fetch_certs.return_value,
82+
audience=mock.sentinel.audience)
83+
84+
85+
@mock.patch('google.oauth2.id_token.verify_token', autospec=True)
86+
def test_verify_oauth2_token(verify_token):
87+
result = id_token.verify_oauth2_token(
88+
mock.sentinel.token,
89+
mock.sentinel.request,
90+
audience=mock.sentinel.audience)
91+
92+
assert result == verify_token.return_value
93+
verify_token.assert_called_once_with(
94+
mock.sentinel.token,
95+
mock.sentinel.request,
96+
audience=mock.sentinel.audience,
97+
certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL)
98+
99+
100+
@mock.patch('google.oauth2.id_token.verify_token', autospec=True)
101+
def test_verify_firebase_token(verify_token):
102+
result = id_token.verify_firebase_token(
103+
mock.sentinel.token,
104+
mock.sentinel.request,
105+
audience=mock.sentinel.audience)
106+
107+
assert result == verify_token.return_value
108+
verify_token.assert_called_once_with(
109+
mock.sentinel.token,
110+
mock.sentinel.request,
111+
audience=mock.sentinel.audience,
112+
certs_url=id_token._GOOGLE_APIS_CERTS_URL)

0 commit comments

Comments
 (0)