Service Examples
Service examples cover scenarios in which a program wishes to use
the planet auth utilities as a service, verifying the authenticity
of access credentials presented to the service by a client.
It should be noted that Services may also act as clients when making
calls to other services. When a service is acting in such a capacity,
the Client Examples apply.
When the service is acting on behalf of itself, it is likely that
a OAuth2 Client Credentials
configuration applies.
When a service is acting on behalf of one of its clients...
(TODO: cover this.)
Verifying OAuth Clients
The planet_auth.OidcMultiIssuerValidator class is provided to assist with
common OAuth client authentication scenarios. This class can be configured
with a single authority for normal operations, and may optionally be configured
with a secondary authorities. This allows for complex deployments such as
the seamless migration between auth servers over time.
This utility class may be configured for entirely local token validation,
or may be configured to check token validity against the OAuth token inspection
endpoint. For most operations, local validation is expected to be used, as
it is more performant, not needing to make blocking network calls, and more
robust, not depending on external service availability. For high value operations,
remote validation may be performed which checks whether the specific access
token has been revoked.
Tokens are normally not long-lived. Token lifespans should be selected to
strike a balance between security concerns and allow frequent use of local
validation, and not overburden the token inspection endpoint.
Checking tokens against the OAuth token inspection endpoint does require the
use of OAuth clients that are authorized to use the endpoint, and may not
be available to anonymous clients, depending on the auth server configuration.
Local Access Token Validation
| Basic usage of OidcMultiIssuerValidator. Validate access tokens locally. |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 | import http
import logging
import os
import re
from flask import Flask, make_response, request
from functools import wraps
from typing import List, Optional
import planet_auth
#############################################################################
# Logging Configuration
#############################################################################
# In a simple environment, the global logging format will be
# applied. This example removes all formatting around the log message from
# the outer application, allowing the library's logging to be emitted with
# no further decoration. The auth library will emit logs in a json
# format, so removing surrounding formatting insures that the json will be
# parsable:
logging.basicConfig(format="%(message)s", level=logging.DEBUG)
# Alternatively, we can tell the library to use python structured logging.
# The exact appearance of the logs in this case will be dependent on the
# configuration of the logger. When structured loging is used, the library
# will log using the python logger's `extra` data. In most cases,
# this would be expected to be done in conjunction with a call to
# setPyLoggerForAuthLogger() (see below).
#
# planet_auth.setStructuredLogging()
# What logger will be used can also be controlled. By default, the library
# will use a logger it creates internally that is global to the library.
# Applications may set the logger for the library to "None" to quite all,
# or may set it to any logger of their choice.:
#
# planet_auth.setPyLoggerForAuthLogger(None)
# or
# planet_auth.setPyLoggerForAuthLogger(logging.getLogger(name="my-custom-auth-lib-logger"))
#############################################################################
# Validator Configuration
#############################################################################
# Never ever ever EVER NEVER EVER accept authorities that cross trust realms
# in the same service. This is a recipe for compromising the integrity of
# your secure environments.
#
# NEVER. NEVER EVER.
# Do not cross the streams.
# Seriously. Don't do it.
auth_validator = planet_auth.OidcMultiIssuerValidator.from_auth_server_configs(
trusted_auth_server_configs=[
{
"auth_server": os.getenv("MY_TRUSTED_ISSUER_PRIMARY"),
"audiences": [os.getenv("MY_TRUSTED_AUDIENCE_PRIMARY")],
},
{
"auth_server": os.getenv("MY_TRUSTED_ISSUER_DEPRECATED"),
"audiences": [os.getenv("MY_TRUSTED_AUDIENCE_DEPRECATED")],
},
],
)
#############################################################################
# Connecting validator to flask using decorators (your service needs may vary)
#############################################################################
# Note: This example only enforces basic authentication - that the user has
# authenticated to a trusted auth server and presented a valid access
# token. In practice, services are also responsible for determining
# whether that particular client user is allowed to make a particular
# request. Services may have their own database or configuration that
# governs this. The overall service architecture may define token
# claims that convey particular privileges. Neither of these are
# demonstrated by this example.
@planet_auth.AuthException.recast(ValueError)
def flask_auth_worker(do_remote_check: bool, scopes_any_of=Optional[List[str]]):
auth_header = request.headers.get("Authorization")
if not auth_header:
raise planet_auth.AuthException("Authorization header not provided")
scheme, token = re.split(" +", auth_header.strip(), 1)
if scheme != "Bearer":
raise planet_auth.AuthException("Unrecognized authentication token scheme: {}".format(scheme))
# this call will throw auth failures
return auth_validator.validate_access_token(
token=token, do_remote_revocation_check=do_remote_check, scopes_anyof=scopes_any_of
)
def flask_auth_decorator(do_remote_check: bool = False, scopes_any_of=None):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kws):
try:
# This example does not pass the results of the
# validation back to the flask app at this time.
local_validation, remote_validation = flask_auth_worker(
do_remote_check=do_remote_check, scopes_any_of=scopes_any_of
)
# If you have additional application specific checks
# you wish to make at the granularity of the decorator,
# this is a reasonable place to do them. After having
# passed the checks above, the contents of the token
# as returned in local_validation / remote_validation
# can be considered trusted.
except planet_auth.AuthException as ae:
# Don't leak information to unauthorized clients in the response.
# It's fine to be more detailed in server side logs.
logging.warning("Auth failure: {}".format(ae))
# Return or abort, as per your application conventions.
return make_response("UNAUTHORIZED", http.HTTPStatus.UNAUTHORIZED)
# abort(http.HTTPStatus.UNAUTHORIZED, "UNAUTHORIZED")
return fn(*args, **kws)
return wrapper
return decorator
#############################################################################
# Main flask application
#############################################################################
app = Flask(__name__)
# Order of decorators matters
@app.route("/require-scopes")
@flask_auth_decorator(scopes_any_of=["required_scope1", "required_scope2"])
def require_scopes():
return "Hello World! from a scope restricted endpoint.\n"
@app.route("/")
@flask_auth_decorator(scopes_any_of=None)
def hello():
return "Hello World!\n"
if __name__ == "__main__":
# Control how the library emits its logs:
# planet_auth.setStringLogging()
# planet_auth.setStructuredLogging()
app.run(host="localhost", port=5001)
|
Local and Remote Access Token Validation
| Advanced usage of OidcMultiIssuerValidator. Validate access tokens against OAuth inspection endpoints using custom auth clients. |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 | import http
import logging
import planet_auth
import re
from flask import Flask, make_response, request
from functools import wraps
#############################################################################
# Logging Configuration
#############################################################################
# In a simple environment, the global logging format will be
# applied. This example removes all formatting around the log message from
# the outer application, allowing the library's logging to be emitted with
# no further decoration. The auth library will emit logs in a json
# format, so removing surrounding formatting insures that the json will be
# parsable:
logging.basicConfig(format="%(message)s", level=logging.DEBUG)
# Alternatively, we can tell the library to use python structured logging.
# The exact appearance of the logs in this case will be dependent on the
# configuration of the logger. When structured loging is used, the library
# will log using the python logger's `extra` data. In most cases,
# this would be expected to be done in conjunction with a call to
# setPyLoggerForAuthLogger() (see below).
#
# planet_auth.setStructuredLogging()
# What logger will be used can also be controlled. By default, the library
# will use a logger it creates internally that is global to the library.
# Applications may set the logger for the library to "None" to quite all,
# or may set it to any logger of their choice.:
#
# planet_auth.setPyLoggerForAuthLogger(None)
# or
# planet_auth.setPyLoggerForAuthLogger(logging.getLogger(name="my-custom-auth-lib-logger"))
#############################################################################
# Validator Configuration
#############################################################################
# In order to perform remote token validation with the auth server, we must
# be registered with the auth server as a client ourselves, even if we never
# intend to act as a client and obtain access tokens of our own. In this
# example, we are registered as a confidential client (one with a client
# secret) that is permitted client credentials OAuth flow and grant type.
# This is not the only possibility.
# TODO: we should have an example of how to use a built-in provider to provide
# named application server trust environments through use of the
# planet_auth_utils.PlanetAuthFactory.initialize_resource_server_validator
validator_auth_client_config = {
"client_type": "oidc_client_credentials_secret",
"auth_server": "_trusted_auth_server_issuer_",
"audiences": ["_expected_token_audience_"],
"client_id": "_your_client_id_",
"client_secret": "_your_client_secret_",
}
auth_validator = planet_auth.OidcMultiIssuerValidator.from_auth_server_configs(
trusted_auth_server_configs=[validator_auth_client_config]
)
#############################################################################
# Connecting validator to flask using decorators (your service needs may vary)
#############################################################################
# Note: This example only enforces basic authentication - that the user has
# authenticated to a trusted auth server and presented a valid access
# token. In practice, services are also responsible for determining
# whether that particular client user is allowed to make a particular
# request. Services may have their own database or configuration that
# governs this. The overall service architecture may define token
# claims that convey particular privileges. Neither of these are
# demonstrated by this example.
@planet_auth.AuthException.recast(ValueError)
def flask_auth_worker(do_remote_check: bool):
auth_header = request.headers.get("Authorization")
if not auth_header:
raise planet_auth.AuthException("Authorization header not provided")
scheme, token = re.split(" +", auth_header.strip(), 1)
if scheme != "Bearer":
raise planet_auth.AuthException("Unrecognized authentication token scheme: {}".format(scheme))
# throws auth failures
return auth_validator.validate_access_token(token=token, do_remote_revocation_check=do_remote_check)
def flask_auth_decorator(do_remote_check: bool = False):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kws):
try:
local_validation, remote_validation = flask_auth_worker(do_remote_check)
except planet_auth.AuthException as ae:
# Don't leak information to unauthorized clients in the response.
# It's fine to be more detailed in server side logs.
logging.warning("Auth failure: {}".format(ae))
# Return or abort, as per your application conventions.
return make_response("UNAUTHORIZED", http.HTTPStatus.UNAUTHORIZED)
# abort(http.HTTPStatus.UNAUTHORIZED, "UNAUTHORIZED")
return fn(*args, **kws)
return wrapper
return decorator
#############################################################################
# Main flask application
#############################################################################
app = Flask(__name__)
# Order of decorators matters
@app.route("/")
@flask_auth_decorator()
def hello():
return "Hello World!"
@app.route("/secret")
@flask_auth_decorator(do_remote_check=True)
def secret():
return "Sensitive info. Extra checks performed for revoked access tokens."
if __name__ == "__main__":
app.run(host="localhost", port=5001)
|
Verifying Planet Legacy Client
This is not supported by this library.