MIT itself and MIT-adjacent organizations offer many useful web services through a Single-Sign-On (SSO) service called Touchstone, powered by Okta with two-factor logins provided by Duo. This is great, and allows easy access to many functionalities, but because MIT retrofit Okta into its existing bespoke infrastructure, there is limited ability to access Touchstone-protected sites without a web browser.
Enter touchstone-auth, a Python package powered mostly by the requests
package! This lets user authenticate themselves programmatically. Cookies are cached,
meaning that re-authentication is only needed once cookies expire.
This package is on Pip, so you can just:
pip install touchstone-auth
Alternatively, you can get built wheels from the Releases tab on Github.
N.B. if installing manually, requests_pkcs12 must be version v1.10 (pip install handles this automatically).
The class TouchstoneSession is simply a requests.Session that performs the Touchstone
authentication flow before returning a working session to you, the authenticated user.
It is easiest to use as a context manager. Because Touchstone authentication is logging in as yourself, remember to not hard-code your credentials and certainly don't commit them to git!
The example here loads credentials from a json file called credentials.json:
{
"username": "<kerb>",
"password": "<password>"
}
Then, in your Python file, you can do the following:
import json
from touchstone_auth import TouchstoneSession, CertificateAuth
with open('credentials.json') as config_file:
config = json.load(config_file)
with TouchstoneSession(
base_url='https://atlas.mit.edu',
auth_type=UsernamePassAuth(config['username'], config['password']),
cookiejar_filename='cookies.pickle',
verbose=True) as s:
response = s.get('https://atlas.mit.edu/atlas/Main.action')
When you call this the first time, your Python script will hang on the 2FA step until the second-factor (by default, Duo push) is accepted. Subsequent requests should not block until the 30-day "remember me" period is exceeded.
If this blocking behavior is undesired, you can set the argument should_block=False
in the TouchstoneSession constructor. If a blocking 2FA push is required, the error
WouldBlockError will instead be raised.
Finally, there is a verbose argument; setting verbose=True will output extra
information about how processing is proceeding.
To use your username and password (do not hard code your credentials in your code!), pass
a UsernamePassAuth instead:
with TouchstoneSession(
base_url='...',
auth_type=UsernamePassAuth(kerb_account, kerb_password),
cookiejar_filename='cookies.pickle') as s:
Certain old MIT websites do still use the bespoke MIT system. If you are accessing one of these systems that has not been upgraded to use Okta yet, you can use one of these auth methods. There's no guarantee that this will remain stable, but we retain the auth methods here.
If you have your certificate as a byte string instead of a filename, just pass the bytes as your certificate:
with TouchstoneSession(
base_url='...',
auth_type=CertificateAuth(cert_bytes, cert_password),
cookiejar_filename='cookies.pickle') as s:
To authenticate using Kerberos tickets, pass KerberosAuth() as the auth_type parameter to
TouchstoneSession, as in:
with TouchstoneSession(
base_url='...',
auth_type=KerberosAuth(),
cookiejar_filename='cookies.pickle') as s:
import json
from touchstone_auth import TouchstoneSession, UsernamePassAuth
with open('credentials.json') as cred_file:
credentials = json.load(cred_file)
with TouchstoneSession(
base_url='https://myadp.mit.edu',
auth_type=UsernamePassAuth(credentials['username'], credentials['password']),
cookiejar_filename='cookies.pickle') as s:
response = s.get('https://my.adp.com/myadp_prefix/v1_0/O/A/payStatements?adjustments=yes&numberoflastpaydates=160')
response_json = json.loads(response.text)
latest = response_json['payStatements'][0]
print('Latest paystub ({}): ${} net, ${} gross'.format(
latest['payDate'],
latest['netPayAmount']['amountValue'],
latest['grossPayAmount']['amountValue']))
which returns
Latest paystub (2021-08-13): $XXXX.XX net, $YYYY.YY gross when run.
import json
from touchstone_auth import TouchstoneSession, UsernamePassAuth
with open('credentials.json') as cred_file:
credentials = json.load(cred_file)
with TouchstoneSession(
base_url=r'https://atlas-auth.mit.edu/oauth2/authorize?identity_provider=Touchstone&redirect_uri=https://covidpass.mit.edu&response_type=TOKEN&client_id=2ao42ccnajj7jpqd7h059n7eoc&scope=covid19/impersonate covid19/user digital-id/search digital-id/user openid profile',
auth_type=UsernamePassAuth(credentials['username'], credentials['password']),
cookiejar_filename='cookies.pickle') as s:
response = json.loads(s.get('https://api.mit.edu/pass-v1/pass/access_status').text)
print('Current Covidpass status: {}'.format(response['status']))
This returns Current Covidpass status: access_granted if you are in fact up to date on Covidpass.
For the various "new Atlas" OAUTH2 applications, you need to find the relevant authorization URL to put as the base URL.
How did I find the proper URL for Covidpass? By looking in your browser's Developer Tools, you can locate the last GET request prior to redirect to idp.mit.edu, then remove the extraneous state parameter.
from touchstone_auth import TouchstoneSession, UsernamePassAuth
from bs4 import BeautifulSoup
with open('credentials.json') as cred_file:
credentials = json.load(cred_file)
with TouchstoneSession(base_url='https://student.mit.edu/',
auth_type=UsernamePassAuth(config['username'], config['password']),
cookiejar_filename='cookies.pickle') as s:
payload = {'termcode': '2023FA', 'SUBJECT01': '6.1600'}
headers = {'Referer': 'https://student.mit.edu/cgi-bin/sfprwlst_sel.sh'}
r = s.post('https://student.mit.edu/cgi-bin/sfprwlst.sh', data=payload, headers=headers)
print(BeautifulSoup(r.text, 'html.parser').pre.text)
With version 0.3.0, you can also select between phone-call and Duo Push two factor
authentication. touchstone-auth defaults to Duo Push if you do not select one.
To switch between the two, pass an additional twofactor_auth argument. For example,
to use the phone-call two factor method in the above example, additionally import
the TwofactorType enum and pass it to the session constructor:
import json
from touchstone_auth import TouchstoneSession, UsernamePassAuth, TwofactorType
with open('credentials.json') as cred_file:
credentials = json.load(cred_file)
with TouchstoneSession(
base_url=r'https://atlas-auth.mit.edu/oauth2/authorize?identity_provider=Touchstone&redirect_uri=https://covidpass.mit.edu&response_type=TOKEN&client_id=2ao42ccnajj7jpqd7h059n7eoc&scope=covid19/impersonate covid19/user digital-id/search digital-id/user openid profile',
auth_type=UsernamePassAuth(credentials['username'], credentials['password']),
cookiejar_filename='cookies.pickle',
twofactor_type=TwofactorType.PHONE_CALL) as s:
response = json.loads(s.get('https://api.mit.edu/pass-v1/pass/access_status').text)
print('Current Covidpass status: {}'.format(response['status']))
If you'd like to hack locally on touchstone-auth, after cloning this repository:
$ git clone https://github.com/meson800/touchstone-auth.git
$ cd git
you can create a local virtual environment, and install touchstone-auth in "development mode"
$ python -m venv env
$ .\env\Scripts\activate (on Windows)
$ source env/bin/activate (on Mac/Linux)
$ pip install -e .
After this 'local install', you can use and import touchstone-auth freely without
having to re-install after each update.
You can run this package as a module to generate debug output. By default:
python -m touchstone_auth
attempts to login to https://atlas.mit.edu using the credentials in credentials.json and prints out the title of the resulting HTML.
You can change the target and the credential file with the --url and --credentials arguments.
This is useful for a sanity check, as it returns the verbose debug information.
- New MIT applications may not use Shibboleth going forward. This library does not currently support non-Shibboleth applications, but this is an intended feature for 0.9.0
See the CHANGELOG for detailed changes.
## [0.8.0] - 2024-06-23
### Updated
- Now works with the new Okta Touchstone flow, for applications that use the Shibboleth proxy.
### Added
- The library can now be run as a module (python -m touchstone_auth) for debugging purposes.
This is licensed by the MIT license. Use freely!