10

I have a Python service which imports a library that talks to the PayPal API. There is a config file that is passed into the library __init__() which contains the PayPal API username and password.

Calling the PayPal API token endpoint with the username and password will return a token used to authenticate during the pay call. However, this token lasts for 90 minutes and should be reused.

There are multiple instances of this service running on different servers and they need to all share this one secret token.

What would the best way of storing this 9 minute token be?

user7692855
  • 1,582
  • 5
  • 19
  • 39
  • *Since the library is instantiated every time a Payment is to be made*: no Python modules are not instantiated each time. They persist in memory in the `sys.modules` structure. The library object doesn't need recreating either, you can simply store it as a global. You haven't shared what library you are using here, is the official [Python PayPal SDK](https://github.com/paypal/PayPal-Python-SDK)? Or are you saying your *service* is run as a new Python process each time? – Martijn Pieters Mar 20 '19 at 12:25
  • Please add that to your question, that's a hugely important detail. – Martijn Pieters Mar 21 '19 at 11:01
  • 1
    Not a general answer, but if your servers are running on AWS, you could use the [Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html). – Elias Strehle Mar 24 '19 at 21:02
  • Where are you hosting the service? Most cloud providers provide some sort of object store (S3), in-memory database (Elasticache) or a parameter store that are all common options for storing configuration that needs to be shared by multiple instances. – mihow Mar 26 '19 at 22:26

2 Answers2

15

While you could persist this in a database, since it's only valid for 90 minutes, you might consider using an in-memory data store like Redis. It's very simple to set up and there are various Python clients available.

Redis in particular supports expiration time when setting a value, so you can make sure it'll only be kept for a set amount of time. Of course, you should still have exception handling in place in case for some reason the key is invalidated early.

While this may introduce a software dependency if you're not already using a key-value store, it's not clear from your question how this library is intended to be used and thus whether this is an issue.

If installing other software is not an option, you could use a temporary file. However, because Python's tempfile doesn't seem to support directly setting a temporary file's name, you might have to handle file management manually. For example:

import os
import time
import tempfile


# 90 minutes in seconds. Setting this a little lower would 
# probably be better to account for network latency.
MAX_AGE = 90 * 60
# /tmp/libname/ needs to exist for this to work; creating it
# if necessary shouldn't give you much trouble.
TOKEN_PATH = os.path.join(
    tempfile.gettempdir(), 
    'libname', 
    'paypal.token',
)


def get_paypal_token():
    token = None

    if os.path.isfile(TOKEN_PATH):
        token_age = time.time() - os.path.getmtime(TOKEN_PATH)

        if token_age < MAX_AGE:
            with open(TOKEN_PATH, 'r') as infile:
                # You might consider a test API call to establish token validity here.
                token = infile.read()

    if not token:
        # Get a token from the PayPal API and write it to TOKEN_PATH.
        token = 'dummy'

        with open(TOKEN_PATH, 'w') as outfile:
            outfile.write(token)

    return token

Depending on the environment, you would probably want to look into restricting permissions on this temp file. Regardless of how you persist the token, though, this code should be a useful example. I wouldn't be thrilled about sticking something like this on the file system, but if you already have the PayPal credentials used to request a token on disk, writing the token to temporary storage probably won't be a big deal.

kungphu
  • 4,592
  • 3
  • 28
  • 37
  • What would you suggest @MartijnPieters ? – user7692855 Mar 20 '19 at 21:10
  • 2
    As it turns out, it's not just a single long-running service, but a cluster of web servers, so using Redis to share the token between them is indeed correct. – Martijn Pieters Mar 21 '19 at 11:02
  • That's definitely important info. I'm going to leave the temp file part of my answer there just because the question is vague enough that it could be useful to people who find it. – kungphu Mar 21 '19 at 13:05
  • 1
    This was really helpful, solved my problem! Redis is interesting... – glitchwizard Feb 22 '21 at 00:23
  • 1
    @glitchwizard Glad it helped! Redis is a wonderful utility. While I really don't like the fact that its stored procedures are also volatile data, I've gotten a LOT of utility out of it, and definitely recommend it for situations where your project calls for a key-value store. – kungphu Feb 23 '21 at 07:33
  • @kungphu so if I restart my machine, the data is wiped right? – glitchwizard Feb 23 '21 at 16:51
  • 1
    @glitchwizard Only if you have it set up that way. If you search for `redis persistence`, the documentation does a good job of explaining the options for avoiding wipes in the event you want to keep the data. – kungphu Feb 24 '21 at 02:20
  • @kungphu what you do use to the key for the token value stored in Redis, what is a good naming convention? – Zaffer Dec 09 '22 at 16:40
2

You could store the token as a system variable.

import os

# Store token
os.environ['PAYPAL_API_TOKEN'] = <...>

# Retrieve token
token = os.environ['PAYPAL_API_TOKEN']

Be aware of the security implications though: Other processes could read the token.

Elias Strehle
  • 1,722
  • 1
  • 21
  • 34
  • 2
    The provided example does not work across processes. It may be possible to access another process' env, but it's not something I've seen done, and you'd then need some other way to track the PID of the reference process... also, OP is apparently deploying this on multiple servers for a single application, so a machine-independent method would be better. – kungphu Mar 23 '19 at 07:40
  • 1
    This sounds like a large security risk ... I'm not sure any outcome could justify storing this information unencrypted like that ... especially with what must be very secure official PayPal implementations... – Michael Treanor Mar 26 '19 at 12:28
  • @MichaelTreanor In practice, I was unable to read environmental variables from one python process to another. The environmental variables created within one python process appear to be accessible only by that single specific process, and the environmental variables are destroyed when the process ends. – Patrick Conwell Aug 30 '22 at 19:00