Lesson 2 of 6

API Authentication and Sessions

Objective

In this lesson you will learn how to authenticate to the SD‑WAN Manager (vManage) REST API, establish and maintain an API session, retrieve the CSRF token required by the API, and use the Python requests library to make authenticated calls. This matters in production because API authentication and session handling protect management-plane operations and avoid accidental loss of access; automation scripts that do not properly handle sessions cause incomplete changes, failed orchestrations, or security exposure. Real-world scenario: a network automation pipeline polls vManage inventory and modifies templates nightly — reliable authentication and token handling are mandatory so the pipeline can safely run unattended.

Quick Recap

We continue with the same topology used in Lesson 1. No new devices are added in this lesson.

ASCII topology (addresses shown are the S‑IP management addresses used by the SD‑WAN fabric):

                       +------------------------+
                       |   SD-WAN Manager       |
                       |   vManage (S-IP)       |
                       |   100.0.0.1/32         |
                       +-----------+------------+
                                   |
                      ................ management ................
                                   |
    +-----------+      +----------+----------+      +-----------+
    | Hub1      |      |   Controller /      |      | Hub2      |
    | on-prem   |      |   Validator (S-IP)  |      | SIG       |
    | S-IP:     |      |   100.0.0.101/32    |      | S-IP:     |
    | 10.0.0.1/32|     +---------------------+      | 10.0.0.2/32|
    +-----------+                                     +-----------+
         |                                                 |
         | MPLS/Internet                                  |
         |                                                 |
  +------+----+    +------+----+    +------+----+    +------+
  | Spoke1    |    | Spoke2    |    | Spoke3    |    |Spoke4|
  | S-IP:     |    | S-IP:     |    | S-IP:     |    |S-IP: |
  | 10.0.0.3/32|   | 10.0.0.4/32|   | 10.0.0.5/32|   |10.0.0.4/32|
  +-----------+    +-----------+    +-----------+    +------+

Additional networks referenced:
- 172.16.1.0/24
- 172.16.2.0/24

Device table

DeviceRoleManagement S‑IP
vManageSD‑WAN Manager (API server)100.0.0.1/32
ControllervBond / Controller role100.0.0.101/32
ValidatorValidation service100.0.0.101/32
Hub1 (onprem)Hub10.0.0.1/32
Hub2 (SIG)Hub10.0.0.2/32
Spoke1Edge10.0.0.3/32
Spoke2Edge10.0.0.4/32
Spoke3Edge10.0.0.5/32
Spoke4Edge10.0.0.4/32

Credentials used in examples:

  • Username: admin
  • Password: Lab@123
  • Domain name examples: lab.nhprep.com
  • Organization: NHPREP

Tip: In production, credentials are stored and retrieved from a secrets manager rather than hard-coded in scripts. For the lab we use admin / Lab@123 so you can replicate the examples.


Key Concepts

  • Session (stateful client object): When using REST APIs from Python, a requests.Session() object keeps cookies and connection pooling. vManage sets a session cookie (JSESSIONID) on successful login; using a session object preserves that cookie for subsequent calls.

    • Packet-level note: the JSESSIONID cookie is returned in the Set-Cookie header of the HTTPS response from the vManage server; subsequent requests include that cookie in the Cookie header.
  • CSRF (Cross‑Site Request Forgery) token: vManage requires a CSRF token (retrieved from /dataservice/client/token) to be sent as the header X-XSRF-TOKEN with state-changing or some read API calls. This prevents forged cross-origin requests by verifying the client holds the token tied to its session cookie.

  • Authentication flow: Typical flow — POST credentials to the security endpoint, receive JSESSIONID cookie → GET CSRF token endpoint → include token + cookie in API calls. If the session expires, the server returns 401/403 and the client must re-authenticate.

  • Requests behavior: requests.Session() transparently handles TLS handshakes and cookie persistence. For lab testing with self-signed certificates, you may disable certificate validation (verify=False) but in production you must validate server TLS certs.

  • Why sessions matter in production: Long-running automation tasks must detect expired sessions and re-authenticate automatically. Failure to do so leads to failed API calls, partial changes, or automation retries that create duplicated operations.


Step-by-step configuration

Step 1: Create a Python session and authenticate to vManage

What we are doing: We create a requests.Session, then POST the username/password to the vManage authentication endpoint to establish a server session. This yields a server cookie (JSESSIONID) that authenticates subsequent API calls.

#!/usr/bin/env python3
import requests

# Create a session object to persist cookies and connections
session = requests.Session()
session.verify = False  # lab only; in production, validate TLS certificates

vmanage_host = "https://100.0.0.1"
auth_url = vmanage_host + "/j_security_check"
credentials = {
    "j_username": "admin",
    "j_password": "Lab@123"
}

# POST credentials to establish a session
response = session.post(auth_url, data=credentials)
print("HTTP status code:", response.status_code)
print("Response headers:", response.headers)
print("Session cookies after login:", session.cookies.get_dict())

What just happened: The POST to /j_security_check sends the username and password in form-encoded format over HTTPS. On successful authentication the vManage server sets a cookie (typically JSESSIONID) in the Set-Cookie response header. The requests.Session() stores that cookie and will include it in subsequent requests. Disabling verify is acceptable in a lab but not in production; in production TLS cert validation prevents man-in-the-middle attacks.

Real-world note: In a production environment you should not disable TLS verification. Store credentials in a secure vault and use short-lived API tokens where possible.

Verify:

# Expected output printed by the script (exact formatting may vary by Python/requests version)
HTTP status code: 200
Response headers: {'Date': 'Thu, 01 Jan 1970 00:00:00 GMT', 'Content-Type': 'text/html;charset=UTF-8', 'Set-Cookie': 'JSESSIONID=ABCDEF1234567890; Path=/; HttpOnly', 'Content-Length': '0'}
Session cookies after login: {'JSESSIONID': 'ABCDEF1234567890'}
  • The presence of a JSESSIONID cookie in session.cookies confirms the server accepted the credentials and a session is established.

Step 2: Retrieve the CSRF token from vManage

What we are doing: Request the CSRF token using the authenticated session; vManage returns the token as plain text. The token must be sent in the X-XSRF-TOKEN header on subsequent API calls to prevent CSRF attacks.

# Use the same session from Step 1
token_url = vmanage_host + "/dataservice/client/token"
token_response = session.get(token_url)
csrf_token = token_response.text
print("HTTP status code (token):", token_response.status_code)
print("CSRF token value:", csrf_token)

What just happened: The GET to /dataservice/client/token uses the session cookie to associate the request with the server session. The server responds with a short token string (often a UUID-like value) that must be presented in the X-XSRF-TOKEN header. This two-part check (cookie + header) defends against cross-site request forgery: possession of the cookie alone is not sufficient without the token.

Real-world note: Tokens typically change when the session is recreated; always fetch a fresh token after login or re-login.

Verify:

# Expected output
HTTP status code (token): 200
CSRF token value: 9f8b7e6a-1234-4a1b-9abc-0d1e2f3a4b5c
  • A 200 response and a non-empty token string confirm we are ready to call dataservice APIs.

Step 3: Perform an authenticated API call — retrieve device inventory

What we are doing: We query the device inventory endpoint /dataservice/device using the session cookie and the CSRF token header to demonstrate a complete authenticated request and receive JSON listing devices.

# Construct headers including CSRF token
headers = {
    "X-XSRF-TOKEN": csrf_token,
    "Content-Type": "application/json"
}
devices_url = vmanage_host + "/dataservice/device"

# GET device inventory
devices_response = session.get(devices_url, headers=headers)
print("HTTP status code (devices):", devices_response.status_code)
print("Response body (devices):")
print(devices_response.text)

What just happened: The GET request includes both the cookie (handled by session) and the X-XSRF-TOKEN header. vManage validates the token against the session and returns the device inventory in JSON format. The requests library automatically decodes the HTTP response body for us. API calls that make changes or access sensitive state require both cookie and token; reads often require the token as well.

Real-world note: Use pagination or filtering for large inventories to avoid large payloads. The dataservice APIs often accept query parameters to limit results.

Verify:

# Expected output (example JSON, full response shown)
HTTP status code (devices): 200
Response body (devices):
{
  "data": [
    {
      "host-name": "vManage",
      "system-ip": "100.0.0.1",
      "device-type": "vmanage",
      "uuid": "vm-0001",
      "personality": "vManager"
    },
    {
      "host-name": "controller",
      "system-ip": "100.0.0.101",
      "device-type": "controller",
      "uuid": "ctrl-0001",
      "personality": "controller"
    },
    {
      "host-name": "hub1-onprem",
      "system-ip": "10.0.0.1",
      "device-type": "vedge",
      "uuid": "hub1-0001",
      "personality": "hub"
    },
    {
      "host-name": "hub2-sig",
      "system-ip": "10.0.0.2",
      "device-type": "vedge",
      "uuid": "hub2-0001",
      "personality": "hub"
    },
    {
      "host-name": "spoke1",
      "system-ip": "10.0.0.3",
      "device-type": "vedge",
      "uuid": "spoke1-0001",
      "personality": "spoke"
    },
    {
      "host-name": "spoke2",
      "system-ip": "10.0.0.4",
      "device-type": "vedge",
      "uuid": "spoke2-0001",
      "personality": "spoke"
    },
    {
      "host-name": "spoke3",
      "system-ip": "10.0.0.5",
      "device-type": "vedge",
      "uuid": "spoke3-0001",
      "personality": "spoke"
    },
    {
      "host-name": "spoke4",
      "system-ip": "10.0.0.4",
      "device-type": "vedge",
      "uuid": "spoke4-0001",
      "personality": "spoke"
    }
  ],
  "version": "2.0"
}
  • A 200 and the JSON data array containing the known S‑IPs confirms successful authenticated retrieval of inventory.

Step 4: Handle session expiration and re-authentication

What we are doing: Demonstrate detecting an expired session (HTTP 401/403), re-running the login sequence, and obtaining a fresh CSRF token. Automation must implement this to avoid interrupted runs.

# Example: detect auth failure and re-authenticate
def get_devices(session, token):
    headers = {"X-XSRF-TOKEN": token, "Content-Type": "application/json"}
    r = session.get(vmanage_host + "/dataservice/device", headers=headers)
    if r.status_code in (401, 403):
        # Session expired or invalid token — re-login
        session.post(vmanage_host + "/j_security_check", data=credentials)
        new_token = session.get(vmanage_host + "/dataservice/client/token").text
        headers["X-XSRF-TOKEN"] = new_token
        r = session.get(vmanage_host + "/dataservice/device", headers=headers)
        print("Re-authenticated, new HTTP status:", r.status_code)
        print("Device call response after re-auth:", r.text)
    else:
        print("Initial device call succeeded, status:", r.status_code)
        print(r.text)

# Call the helper
get_devices(session, csrf_token)

What just happened: The function checks the HTTP status code for authorization failure. On 401/403 it re-posts the credentials to /j_security_check, obtains a new token from /dataservice/client/token, updates headers, and retries the API call. This is a standard recovery pattern; note that blindly retrying without re-authenticating will repeatedly fail.

Real-world note: Log re-authentication events and throttle retries to avoid creating a login storm against vManage during wider outages.

Verify:

# Simulated outputs when initial request fails and re-auth succeeds
Re-authenticated, new HTTP status: 200
Device call response after re-auth: {"data":[ {"host-name":"vManage","system-ip":"100.0.0.1", ... } ], "version":"2.0"}
  • Seeing the Re-authenticated message and a 200 indicates the automation correctly recovered from session expiry.

Step 5: Cleanly close the session

What we are doing: Remove session cookies and close the session object to free resources and avoid leaving stale sessions open on the server.

# Close down session
session.cookies.clear()
session.close()
print("Session cookies after clear:", session.cookies.get_dict())

What just happened: Clearing cookies removes stored authentication state from the client side; closing the requests.Session() releases underlying TCP connections and resources. While vManage will eventually expire server-side sessions, the client should not hold on to credentials longer than necessary.

Real-world note: Servers may have configurable idle timeouts; ensure automation jobs are short-lived or refresh sessions proactively.

Verify:

# Expected output
Session cookies after clear: {}
  • An empty cookie jar confirms the client no longer holds authentication credentials.

Verification Checklist

  • Check 1: Session cookie exists after login — verify session.cookies.get_dict() includes JSESSIONID.
  • Check 2: CSRF token retrieval successful — verify GET https://100.0.0.1/dataservice/client/token returns a non-empty token and HTTP 200.
  • Check 3: Authenticated API call returns inventory — verify GET https://100.0.0.1/dataservice/device returns HTTP 200 and JSON listing devices with S-IP values (100.0.0.1, 100.0.0.101, 10.0.0.1, 10.0.0.2, 10.0.0.3, 10.0.0.4, 10.0.0.5).
  • Check 4: Automation recovers from 401/403 — simulate expiry and verify re-authentication flow results in a successful API call.

Common Mistakes

SymptomCauseFix
No JSESSIONID in session cookies after loginWrong endpoint or bad credentials posted to /j_security_checkVerify credentials (admin / Lab@123 for lab) and ensure POST to https://100.0.0.1/j_security_check with form data j_username / j_password.
CSRF token is empty or missingNot using authenticated session when calling /dataservice/client/tokenEnsure the same requests.Session() instance is used for login and token retrieval so the session cookie is sent.
API calls return 403 despite valid cookieMissing X-XSRF-TOKEN header or token mismatchCall /dataservice/client/token and include the returned token in X-XSRF-TOKEN header for subsequent calls.
Script fails in production due to TLS errorverify=False was used in lab but production requires TLS validationReplace session.verify=False with session.verify='/path/to/ca-bundle.pem' or use a trusted certificate; store certs securely.

Key Takeaways

  • Authenticate to vManage by POSTing credentials to /j_security_check; the server returns a session cookie (JSESSIONID) that must be preserved for subsequent calls.
  • Always fetch the CSRF token from /dataservice/client/token and send it as X-XSRF-TOKEN — vManage requires both the cookie and token to authorise API operations.
  • Use requests.Session() to maintain cookies and connection pooling; implement detection and re-authentication for session expiry (HTTP 401/403) to make automation resilient.
  • In production, protect credentials with a secrets manager, validate TLS certificates, and log authentication events; in the lab you may use admin / Lab@123 and disabled TLS verification for convenience.

Final tip: Think of the session cookie like an authentication stamp and the CSRF token like a second signature — both are needed together. Robust automation checks both and refreshes them as required.


End of Lesson 2 — API Authentication and Sessions.