Forum Discussion

caulfiedd_spurr's avatar
Apr 29, 2020

question of limitation and expiration for rest api token

now I cannot login ltm via rest api , i thought the number of token of my account has reach the maximum .here is an error

login myhost fail b'{"code":401,"message":"remoteSender:http://localhost:8100/shared/authn/login, method:POST ","originalRequestBody":"{\\"username\\":\\"user\\",\\"loginProviderName\\":\\"tmos\\",\\"generation\\":0,\\"lastUpdateMicros\\":0}","referer":"ipaddress","restOperationId":124004534,"kind":":resterrorresponse"}'

here are my questions:

  1. is there any number limitation for number of rest api token a user can apply ? I can see some one say one user can only apply 100 tokens ,
  2. how to check the existing token by GUI, or cli since I cannot login device by rest api.
  3. how to take how long a token will expired ;
  4. is there any way to delete token;
  • It is not obvious from the error message you provided, however, a usual error message you get from authorization error (e.g., incorrect password) is "message": "Authentication failed." The issue may be a bit deeper than you may think. Try restarting the iControl REST framework daemon by running 'tmsh restart sys service restjavad'. If the issue still persists, I recommend you to file a service ticket to F5 support.

  • 1) The maximum number of tokens per user is set to 100 since BIG-IP 13.1. The response JSON body to a token request indicates: "user foo has reached maximum active login tokens" (seems like the response body you pasted does not contain this message, so you may be hitting some other issues).

    2) To find the active tokens on the box, call a GET request to /mgmt/shared/authz/tokens. Use admin user.

    3) The lifespan of token is 1200s (20 min) by default. You can change it by PATCHING the timeout property of the token: e.g. To change the lifetime of the token "AEDEM4TRWHGBET2TWOHM6ZBJKD" to 4200s;

    curl -sk https://$HOST/mgmt/shared/authz/tokens/AEDEM4TRWHGBET2TWOHM6ZBJKD \
      -X PATCH -H "Content-type: application/json" \
      -H "X-F5-Auth-Token: AEDEM4TRWHGBET2TWOHM6ZBJKD" \
      -d '{"timeout" : 4200}'

    Note that the Authentication token is designed to be reused. If you are creating a token for each individual task, you may need to consolidate the tasks into one session and request just one token at the beginning of the session. If that's too tedious, you may want to make the timeout shorter.

    4) You can delete the token by sending a DELETE method: e.g.,

    curl -sku $PASS https://$HOST/mgmt/shared/authz/tokens/2PBX7ROP6H4GE6TQN4CUJVJYZG -X DELETE

    Cheers

    • caulfiedd_spurr's avatar
      caulfiedd_spurr
      Icon for Cirrus rankCirrus

      thanks for you detail reply . look like login failure at my side is not caused by token limit , do you have any idea why login failure ,since I was able to login before . thanks

    • StephanManthey's avatar
      StephanManthey
      Icon for MVP rankMVP

      Hi Satoshi San,

      thanks for the answer and details. If I remember right I tested the ability to extend the tokens lifetime in the past and it didnt work as expected. I will check it again and reply in this thread.

      I will add the deletion of the token to my Ansible tasks in case a token is about to expire.

      (Especially in a device onboarding workflow a time of 20 minutes can be exceeded easily. That´s why I re-run the tasks above at critical points.)

      As I´m re-using the token continuosly, there is little to no risk to exceed the per device limit. But for a clean deployment it makes sense to delete tokens in advance.

      Thanks again & kind regards, Stephan

  • Hi, here is a routine I wrote for Ansible.

    It´s validating an existing token before using it for the following tasks.

    In case it is already invalid or it is expiring soon, a new token will be requested and stored for future use.

    I use it i.e. for device onboarding, configuration and modification tasks.

    Even if you don´t use Ansible it hopefully shows the REST calls applied.

    It´s tested with TMOS v12-v15.

    Cheers, Stephan

    - name: request current token information
      no_log: "{{ logging_disabled }}"
      uri:
        validate_certs: no
        url: https://{{ inventory_hostname }}/mgmt/shared/authz/tokens/{{ device_info[inventory_hostname].token }}
        method: GET
        headers:
          X-F5-Auth-Token: "{{ device_info[inventory_hostname].token }}"
        status_code:
        - 200
        - 401
      register: token_info
      until: (token_info.status == 200) or
             (token_info.status == 401)
      retries: 90
      delay: 10
      when: (device_info is defined) and
            (device_info[inventory_hostname] is defined) and
            (device_info[inventory_hostname].token is defined)
            
    - name: debug current token
      debug:
        msg:
        - "auth status code: {{ token_info.status | default('undefined') }}"
        - "token valid for: {{ (token_info.json.expirationMicros | int - token_info.json.lastUpdateMicros | int) // 1000000 }} seconds"
        - "current token: {{ device_info[inventory_hostname].token | default('undefined') }}"
      when: (device_info is defined) and
            (device_info[inventory_hostname] is defined) and
            (device_info[inventory_hostname].token is defined) and
            (token_info.status != 401)
        
    - name: aquire new token on error or if token would expire soon
      no_log: "{{ logging_disabled }}"
      uri:
        validate_certs: no
        url: https://{{ inventory_hostname }}/mgmt/shared/authn/login
        method: POST
        body_format: json
        body:
          username: admin
          password: "{{ bigip_credentials_secure.admin }}"
          loginProviderName: tmos
      register: token_data
      until: token_data.status == 200
      retries: 60
      delay: 10
      when: (token_info.status is not defined) or
            (token_info.status == 401) or
            ((token_info.status == 200) and (((token_info.json.expirationMicros | int - token_info.json.lastUpdateMicros | int) // 1000000) < 120))
            
    - name: retrieve token from payload and store to data structure
      set_fact:
        device_info: "{{ device_info | default({}) | combine({inventory_hostname: {'token': token_data.json.token.token, 'expiration': token_data.json.token.expirationMicros}}, recursive=True) }}"
      when: (token_data is defined) and
            (token_data.status is defined) and
            (token_data.status == 200)
            
    - name: debug final token
      debug:
        msg:
        - "final token: {{ device_info[inventory_hostname].token | default('undefined') }}"
      when: (device_info is defined) and
            (device_info[inventory_hostname] is defined) and
            (device_info[inventory_hostname].token is defined)
    ...

     

  • It is not obvious from the error message you provided, however, a usual error message you get from authorization error (e.g., incorrect password) is "message": "Authentication failed." The issue may be a bit deeper than you may think. Try restarting the iControl REST framework daemon by running 'tmsh restart sys service restjavad'. If the issue still persists, I recommend you to file a service ticket to F5 support.

    • caulfiedd_spurr's avatar
      caulfiedd_spurr
      Icon for Cirrus rankCirrus

      thanks for your reply.

      I have contact F5 support , it turn out the process on F5 , after restart javarestd , it recover

      • catoverflow's avatar
        catoverflow
        Icon for Altocumulus rankAltocumulus

        Did they tell you what was the reason why you had to restart the service? I am asking you, because I am having a similar problem of 401 unauthorized when consulting via API, and it makes me think about token handling, because it is not something that happens all the time, but sometimes, but after seeing the problem that you had, I restarted the service and it started to work correctly. I'm running version BIG-IP 

        15.1.5.1.
        Thanks in advance,
         
        Cheers
  • Perhaps you just want to remove the token right after running the API operation? 

    The script below is using an auth token to patch a sample configuration and afterwards the token will be deleted automatically:

    # python script: apitest2.py
    # version: 0.2 (2022--05-09)
    # author: Stephan Manthey
    # purpose:
    #   retrieve auth token
    #   modify configuration (enable/disable pool member) with token based auth
    #   delete auth token
    # module requests required (installed via Python PIP):
    #   su -c 'yum install python-pip'
    #   su -c 'sudo pip2 install requests'
    #   su -c 'sudo pip3 install requests'
    # or:
    #   su -c 'yum install python-requests'
    #   su -c 'yum install python3-requests'
    
    import time
    import json
    import requests
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
    
    username = 'username'
    password = 'password'
    bigipdev = '10.100.100.81'
    poolname = 'pool_apitest'
    nodename = '10.10.10.11'
    nodeport = 80
    
    authpath = 'https://{}/mgmt/shared/authn/login'.format(bigipdev)
    conthead = {'Content-Type': 'application/json'}
    authdata = {'username': username, 'password': password, 'loginProviderName': 'tmos'}
    memberup = {'state': 'user-up', 'session': 'user-enabled'}
    memberdown = {'state': 'user-down', 'session': 'user-disabled'}
    
    session = requests.Session()
    
    authtime = time.time()
    tokenrequest = session.post(url=authpath,data=json.dumps(authdata),headers=conthead,verify=False)
    print('got my token:','{:f}'.format(time.time() - authtime))
    # print('token request:',tokenrequest.status_code)
    if tokenrequest.status_code == 200:
      tokendata = tokenrequest.json()
      xauthhead = {'X-F5-Auth-Token': tokendata['token']['token'], 'Content-Type': 'application/json'}
      querypath = 'https://{}/mgmt/tm/ltm/pool/~Common~{}/members/~Common~{}:{}'.format(bigipdev,poolname,nodename,nodeport)
      print('patching now:','{:f}'.format(time.time() - authtime))
      membermodify = session.patch(url=querypath,data=json.dumps(memberup),headers=xauthhead,verify=False)
      print('1st response:','{:f}'.format(time.time() - authtime))
      if membermodify.status_code == 200:
        memberdata = membermodify.json()
        membername = memberdata['name']
        # print('member found:',membername)
      else:
        print('modification error:',membermodify.status_code)
        exit()
      membermodify = session.patch(url=querypath,data=json.dumps(memberdown),headers=xauthhead,verify=False)
      print('2nd response:','{:f}'.format(time.time() - authtime))
      if membermodify.status_code == 200:
        memberdata = membermodify.json()
        membername = memberdata['name']
        # print('member found:',membername)
      else:
        print('modification error:',membermodify.status_code)
        exit()
      tokendelpath = 'https://{}/mgmt/shared/authz/tokens/{}'.format(bigipdev,tokendata['token']['token'])
      tokendelete = session.delete(url=tokendelpath,headers=xauthhead,verify=False)
      # print('token delete:', tokendelete.status_code)
      if tokendelete.status_code != 200:
        print('token delete error')
        exit()
    else:
      print('error: no token provided')
      exit()

    PS: Probably you also want to add some lines to save into the startup configuration