OIDC Authentication Examples#

This section demonstrates various approaches to OIDC (OpenID Connect) authentication using the Authorization Code + PKCE flow with different token storage strategies.

Basic OIDC Login#

Demonstrates how to perform a basic OIDC login. Tokens are kept in memory only and are not persisted.

The login process:

  1. Opens your default browser to the OIDC provider’s login page

  2. You authenticate with your credentials

  3. Redirects back with an authorization code

  4. Exchanges the code for tokens

  5. Tokens are kept in memory only (not persisted)

Code#

# Copyright (C) 2022 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Basic OIDC login example.

Demonstrates how to perform an OIDC login and use the access token.
Tokens are kept in memory only (not persisted).

If these tokens are passed to ``Client`` without ``token_storage``, refreshed
tokens also remain in memory only and are not persisted across runs.
"""

import argparse
import logging

from ansys.hps.client import Client
from ansys.hps.client.auth.api.oidc_login import browser_login

log = logging.getLogger(__name__)


def main(hps_url: str, verify_ssl: bool):
    """Perform OIDC login and log the access token."""
    storage_mode = "memory"

    # Perform login - opens browser for authentication
    tokens = browser_login(hps_url=hps_url, verify_ssl=verify_ssl)

    # Configure Client with explicit in-memory refresh persistence mode.
    _ = Client(
        url=hps_url,
        access_token=tokens["access_token"],
        refresh_token=tokens.get("refresh_token"),
        token_storage=storage_mode,
        verify=verify_ssl,
    )

    # Access token is now available
    log.info("Access Token: %s...", tokens["access_token"][:50])
    log.info("Token Expires In: %s seconds", tokens.get("expires_in"))
    log.info("TLS certificate verification enabled: %s", verify_ssl)
    log.info("Client token_storage is set to 'memory' (refresh updates are in-process only)")

    # Use the access token in your API calls
    # Example: requests.get(url, headers={"Authorization": f"Bearer {tokens['access_token']}"})

    return tokens


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Perform basic OIDC login using browser flow.")
    parser.add_argument("-U", "--hps-url", default="https://localhost:8443/hps")
    parser.add_argument(
        "--insecure",
        action="store_true",
        help="Disable TLS certificate verification for local/self-signed endpoints.",
    )
    args = parser.parse_args()

    logging.basicConfig(level=logging.INFO, format="%(message)s")
    main(hps_url=args.hps_url, verify_ssl=not args.insecure)

Usage#

Run the example:

cd examples/oidc
python basic_login.py

Output:

Access Token: eyJhbGciOiJSUzI1NiIsInR5cCI...
Token Expires In: 3600 seconds

Notes#

  • Tokens are kept in memory only and are not persisted to disk

  • The token is lost when the script exits

  • If you pass these tokens to ansys.hps.client.Client with default

    token_storage=\"memory\", refreshed tokens are also in-memory only and are not persisted across runs

  • For persistent storage, see the examples below

OIDC Login with System Keyring Storage#

Demonstrates how to save OIDC tokens to the system credential manager for secure, persistent storage. This is the recommended storage method for security.

The system credential manager varies by platform:

  • Windows: Credential Manager

  • macOS: Keychain

  • Linux: Secret Service (via python-keyring)

Code#

# Copyright (C) 2022 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""OIDC login with system keyring storage.

Demonstrates how to save refresh-token data to the system credential manager:
- Windows: Credential Manager
- macOS: Keychain
- Linux: Secret Service (via python-keyring)

Requires: pip install keyring

This example also demonstrates creating ``Client`` with
``token_storage=\"keyring\"`` so automatic refresh updates are persisted
to keyring across runs. Access tokens remain memory-only.
"""

import argparse
import logging

from ansys.hps.client import Client
from ansys.hps.client.auth.api.oidc_login import browser_login, save_tokens

log = logging.getLogger(__name__)


def main(hps_url: str, verify_ssl: bool):
    """Perform OIDC login and save tokens to system keyring."""
    storage_mode = "keyring"

    # Perform login
    tokens = browser_login(hps_url=hps_url, verify_ssl=verify_ssl)

    # Save tokens to system keyring (preferred storage method)
    try:
        result = save_tokens(tokens, hps_url=hps_url, storage=storage_mode)
    except RuntimeError as ex:
        log.error("%s", ex)
        return None

    # Configure Client to persist automatic token refresh updates to keyring.
    _ = Client(
        url=hps_url,
        access_token=tokens["access_token"],
        refresh_token=tokens.get("refresh_token"),
        token_storage=storage_mode,
        verify=verify_ssl,
    )

    if result is None:
        log.info("Tokens saved to system keyring")
        log.info("Client token_storage is set to 'keyring' for persistent refresh updates")

    log.info("TLS certificate verification enabled: %s", verify_ssl)
    log.info("Token Expires In: %s seconds", tokens.get("expires_in"))

    return tokens


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Perform OIDC login and persist tokens using system keyring storage."
    )
    parser.add_argument("-U", "--hps-url", default="https://localhost:8443/hps")
    parser.add_argument(
        "--insecure",
        action="store_true",
        help="Disable TLS certificate verification for local/self-signed endpoints.",
    )
    args = parser.parse_args()

    logging.basicConfig(level=logging.INFO, format="%(message)s")
    main(hps_url=args.hps_url, verify_ssl=not args.insecure)

Prerequisites#

Install the keyring package:

pip install keyring

Usage#

Run the example:

cd examples/oidc
python login_with_keyring.py

Output:

Tokens saved to system keyring
Token Expires In: 3600 seconds

Security Benefits#

  • Tokens are encrypted at rest in the system credential manager

  • Credentials are managed by the operating system

  • No plain-text files on disk

  • Automatic cleanup when user logs out (on some systems)

Notes#

  • Requires the keyring package

  • Tokens can be loaded from keyring with

    ansys.hps.client.auth.api.oidc_login.load_tokens() using storage="keyring"

  • For automatic refresh persistence across runs in ansys.hps.client.Client, initialize the client with token_storage=\"keyring\"

OIDC Login with Disk Storage#

Demonstrates how to save OIDC tokens to disk with platform-specific security.

Storage locations:

  • Windows: %USERPROFILE%\.ansys\hps_tokens.json (encrypted with DPAPI)

  • Unix/Linux: ~/.ansys/hps/hps_tokens.json (file permissions 0o600)

Code#

# Copyright (C) 2022 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""OIDC login with disk storage.

Demonstrates how to save tokens to disk with platform-specific security:
- Windows: Encrypted with DPAPI (user-scoped) at %USERPROFILE%\\.ansys\\hps_tokens.json
- Unix/Linux: Plaintext with restrictive permissions (0o600) at ~/.ansys/hps/hps_tokens.json

This example also demonstrates creating ``Client`` with
``token_storage=\"disk\"`` so automatic refresh updates are persisted
to disk across runs. Access tokens remain memory-only.
"""

import argparse
import logging

from ansys.hps.client import Client
from ansys.hps.client.auth.api.oidc_login import browser_login, save_tokens

log = logging.getLogger(__name__)


def main(hps_url: str, verify_ssl: bool):
    """Perform OIDC login and save tokens to disk."""
    storage_mode = "disk"

    # Perform login
    tokens = browser_login(hps_url=hps_url, verify_ssl=verify_ssl)

    # Save tokens to disk
    token_file = save_tokens(tokens, hps_url=hps_url, storage=storage_mode)

    # Configure Client to persist automatic token refresh updates to disk.
    _ = Client(
        url=hps_url,
        access_token=tokens["access_token"],
        refresh_token=tokens.get("refresh_token"),
        token_storage=storage_mode,
        verify=verify_ssl,
    )

    log.info("Tokens saved to: %s", token_file)
    log.info("TLS certificate verification enabled: %s", verify_ssl)
    log.info("Client token_storage is set to 'disk' for persistent refresh updates")
    log.info("Token Expires In: %s seconds", tokens.get("expires_in"))

    return tokens


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Perform OIDC login and persist tokens using disk storage."
    )
    parser.add_argument("-U", "--hps-url", default="https://localhost:8443/hps")
    parser.add_argument(
        "--insecure",
        action="store_true",
        help="Disable TLS certificate verification for local/self-signed endpoints.",
    )
    args = parser.parse_args()

    logging.basicConfig(level=logging.INFO, format="%(message)s")
    main(hps_url=args.hps_url, verify_ssl=not args.insecure)

Usage#

Run the example:

cd examples/oidc
python login_with_disk_storage.py

Output:

Tokens saved to: C:\Users\username\.ansys\hps_tokens.json
Token Expires In: 3600 seconds

Security#

Windows#

On Windows, tokens are encrypted using DPAPI (Data Protection API), which provides user-scoped encryption. The encrypted file is only readable by the user who encrypted it on the same computer.

Unix/Linux#

On Unix/Linux systems, the token file is created with restrictive permissions (0o600). Only the owner can read and write it.

Notes#

  • Tokens persist across script invocations

  • Tokens can be loaded from disk with

    ansys.hps.client.auth.api.oidc_login.load_tokens() using storage="disk"

  • For automatic refresh persistence across runs in ansys.hps.client.Client, initialize the client with token_storage=\"disk\"

  • For higher security, use keyring storage instead

Load and Use Saved Tokens#

Demonstrates how to load previously saved tokens and use them in API calls.

Tokens are loaded from an explicitly selected backend (for example, storage="keyring" or storage="disk").

Code#

# Copyright (C) 2022 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Load and use previously saved tokens.

Demonstrates how to load tokens from an explicitly selected storage backend
and use them in API calls.
"""

import argparse
import logging
import time

from ansys.hps.client.auth.api.oidc_login import load_tokens

log = logging.getLogger(__name__)


def is_token_expired(tokens: dict, buffer_seconds: int = 60) -> bool:
    """Return True when token is expired or near expiry."""
    expires_in = tokens.get("expires_in")
    saved_at = tokens.get("saved_at")

    if expires_in is None or saved_at is None:
        return True

    return (saved_at + expires_in - buffer_seconds) <= time.time()


def main(storage_mode: str, verify_ssl: bool):
    """Load saved tokens and use them."""
    tokens = load_tokens(storage=storage_mode)

    if not tokens:
        log.info("No saved tokens found in %s storage. Please run login first.", storage_mode)
        return

    log.info("Loaded tokens for: %s", tokens.get("hps_url"))

    if not tokens.get("access_token"):
        log.info("No access token is persisted by design. Refresh to obtain a new access token.")
        return

    # Check if token is expired (with 60 second buffer)
    if is_token_expired(tokens, buffer_seconds=60):
        log.info("Token is expired or expiring soon. Please refresh.")
        return

    log.info("Token is valid")
    log.info("Access Token: %s...", tokens["access_token"][:50])
    log.info("TLS certificate verification enabled: %s", verify_ssl)

    # Example: Use the token in API calls
    # response = requests.get(
    #     "https://localhost:8443/hps/api/v1/projects",
    #     headers={"Authorization": f"Bearer {tokens['access_token']}"},
    #     verify=verify_ssl,
    # )


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Load and inspect previously saved OIDC tokens from a selected backend."
    )
    parser.add_argument(
        "-s",
        "--storage",
        default="keyring",
        choices=["keyring", "disk"],
        help="Token storage backend to load from (default: keyring).",
    )
    parser.add_argument(
        "--insecure",
        action="store_true",
        help="Disable TLS certificate verification for local/self-signed endpoints.",
    )
    args = parser.parse_args()

    logging.basicConfig(level=logging.INFO, format="%(message)s")
    main(storage_mode=args.storage, verify_ssl=not args.insecure)

Usage#

First, save tokens using one of the preceding login examples. Then run this example:

cd examples/oidc
python load_saved_tokens.py

Output:

Loaded tokens for: https://localhost:8443/hps
Token is valid
Access Token: eyJhbGciOiJSUzI1NiIsInR5cCI...

Features#

  • Uses an explicit storage selection (keyring or disk)

  • Checks token expiration with configurable buffer (default 60 seconds)

  • Provides access token for use in API calls

Error Handling#

If no saved tokens are found, you’ll see:

No saved tokens found. Please run login first.

If tokens are expired or expiring soon:

Token is expired or expiring soon. Please refresh.

In this case, see the token refresh example below.

Notes#

  • Choose the backend explicitly via load_tokens(storage=...)

  • The buffer parameter (default 60 seconds) provides a safety margin before expiration

  • Use the Authorization header with the access token in API requests

Refresh OIDC Tokens#

Demonstrates how to refresh tokens using the refresh_token grant. This allows you to obtain a new access token without requiring user re-authentication.

The refresh token flow is useful when the access token expires but the refresh token is still valid.

Code#

# Copyright (C) 2022 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Refresh saved tokens.

Demonstrates how to refresh tokens using the refresh_token grant for an
explicitly selected storage backend.
"""

import argparse
import logging

from ansys.hps.client.auth.api.oidc_login import load_tokens, refresh_tokens, save_tokens

log = logging.getLogger(__name__)


def main(storage_mode: str, verify_ssl: bool):
    """Refresh saved tokens."""
    # Load current tokens from selected storage
    current_tokens = load_tokens(storage=storage_mode)

    if not current_tokens:
        log.info("No saved tokens found in %s storage. Please run login first.", storage_mode)
        return

    log.info("Refreshing tokens...")

    # Refresh the tokens from the same selected backend
    new_tokens = refresh_tokens(
        hps_url=current_tokens.get("hps_url"),
        storage=storage_mode,
        verify_ssl=verify_ssl,
    )

    if not new_tokens:
        log.info("Token refresh failed. You may need to login again.")
        return

    # Save refreshed tokens back to the same storage backend
    result = save_tokens(new_tokens, new_tokens.get("hps_url"), storage=storage_mode)

    if storage_mode == "keyring":
        log.info("New tokens saved to system keyring")
    else:
        log.info("New tokens saved to disk at: %s", result)

    log.info("New token expires in: %s seconds", new_tokens.get("expires_in"))
    log.info("New refresh token expires in: %s seconds", new_tokens.get("refresh_expires_in"))
    log.info("TLS certificate verification enabled: %s", verify_ssl)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Refresh saved OIDC tokens from a selected storage backend."
    )
    parser.add_argument(
        "-s",
        "--storage",
        default="keyring",
        choices=["keyring", "disk"],
        help="Token storage backend to use (default: keyring).",
    )
    parser.add_argument(
        "--insecure",
        action="store_true",
        help="Disable TLS certificate verification for local/self-signed endpoints.",
    )
    args = parser.parse_args()

    logging.basicConfig(level=logging.INFO, format="%(message)s")
    main(storage_mode=args.storage, verify_ssl=not args.insecure)

Prerequisites#

You must have previously saved tokens using one of the preceding login examples.

Usage#

Run the example:

cd examples/oidc
python refresh_tokens_example.py

Output:

Refreshing tokens...
New tokens saved to system keyring
New token expires in: 3600 seconds
New refresh token expires in: 86400 seconds

Workflow#

  1. Loads current tokens from the selected storage backend

  2. Uses the refresh_token to obtain new tokens without user interaction

  3. Saves the new tokens back to the same selected backend

This allows your app to:

  • Automatically refresh tokens when they expire

  • Keep the refresh token synchronized across invocations

  • Maintain a valid access token for API calls

Error Handling#

If no saved tokens are found:

No saved tokens found. Please run login first.

If refresh fails (for example, refresh token expired):

Token refresh failed. You may need to login again.

Automation#

To automatically refresh tokens when needed, combine token expiration checking with refresh:

from ansys.hps.client.auth.api.oidc_login import (
    load_tokens,
    _is_token_expired,
    refresh_tokens,
    save_tokens
)

tokens = load_tokens(storage="keyring")
if tokens and _is_token_expired(tokens, buffer_seconds=300):
    # Refresh if expiring in next 5 minutes
    new_tokens = refresh_tokens(storage="keyring")
    if new_tokens:
        save_tokens(new_tokens, new_tokens.get("hps_url"), storage="keyring")

Notes#

  • Refresh tokens typically have a longer expiration time than access tokens

  • If both access and refresh tokens expire, you must login again

  • Always save refreshed tokens back to storage for consistency