10Duke Scale SDK for Java
Loading...
Searching...
No Matches
Use identity-based licensing

10Duke Scale supports identity-based licensing for users: users authenticate themselves and every operation tracks the users' identity. For authentication, 10Duke Scale uses Open ID Connect (OIDC), an authentication framework built on OAuth 2.0. To use identity-based licensing, you need an OIDC-based identity provider service. If you don't have one configured, contact 10Duke sales for options.

The 10Duke Scale SDK for Java provides two types of OIDC authentication: browser-based authentication and OAuth 2.0 "Device Authorization Grant" authentication. Device authentication is primarily intended for scenarios where the device lacks a browser: the user authenticates out-of-band using a browser on another platform. For browser-based authentication, the client uses default OS-browser to perform the login.

Note the following limitations:

  • 10Duke Scale SDK for Java supports OIDC ID tokens for API call authorization, which are signed with RSA keys using RS256. For detailed description of ID token requirements, see 10Duke Scale API documentation.
  • 10Duke Scale SDK for Java supports the following OAuth flows:
    • "Proof Key for Code Exchange by OAuth Public Clients" (see RFC 7636).
    • "OAuth 2.0 Device Authorization Grant" (see RFC 8628)

Browser-based authentication

10Duke Scale SDK for Java provides browser-based OIDC authentication using:

  • operating system default browser
  • a caller defined browser

"Loopback interface redirection" is used as part of the user authentication flow.

Workflow description

The client application opens a browser and starts the login flow with the OIDC provider. The client simultaneously opens a local HTTP server used to detect when the login is complete. After completing the login, the OIDC server issues an HTTP redirect to a preconfigured URL (the redirect URI) in the browser. For details, see RFC 8252: 7.3 Loopback Interface Redirection.

Configuration

The client must be configured with an HTTP message served by the HTTP server after the login is complete. The HTTP message is an HTTP response body (response message entity). The client application must also configure the OIDC provider's JSON Web Key Set URL and the OpenID Connect discovery URL, which advertises the OIDC provider's authorization settings.

Device authentication

If the device running the client lacks a browser, or using the browser is awkward, authentication can be performed using the Device Authorization Grant (or device flow).

Workflow description

To start the user login, the device shows an authentication URL and a user code to the user. Using another device (for example, a computer or tablet), the user starts authentication by navigating to the given URL and entering the user code. Once the authentication is complete, the client is notified and a user session is set up.

For details of the Device Authorization Grant, see RFC 8628.

When authenticating with device flow, the application developer must implement the display of the relevant information for the end user.

Configuration

The client application must configure the OIDC provider's JSON Web Key Set URL and the OpenID Connect discovery URL, which advertises the OIDC provider's authorization settings.

Examples

The process of implementing identity-based license consumption is explained in the 10Duke Scale documentation.

Desktop application example

First, let's look at the main program of this example, a Java CLI application that implements the following flow:

  • initialize the application and 10Duke Scale SDK
  • sign in the user using the OAuth PKCE flow
  • query information to enable selecting a license to check out
  • check out a seat
  • release the checkout
public static void main(final String[] args)
throws IOException, InterruptedException, URISyntaxException {
// Assert that a desktop (browser) is supported
if (!Desktop.isDesktopSupported()) {
throw new UnsupportedOperationException(
"Java AWT Desktop is not supported,"
+ " cannot run user sign-in");
}
CliLicensingExample example = new CliLicensingExample();
// Bootstrap the client app and sdk
example.initClientApp();
// Sign in the user and wait for completion
example.signin();
System.out.println("waiting for login to complete...");
example.loginSemaphore.acquire();
// Select licensee, one of potentially many
example.selectLicensee();
if (example.selectedLicensee == null) {
System.out.println("No licensee selected, bye bye...");
return;
}
// Select a license for checkout
example.selectLicense();
if (example.selectedLicense == null) {
System.out.println("No license selected, bye bye...");
return;
}
// Checkout the license
LicenseToken licenseToken = example.checkoutLicense();
System.out.println(
"License checkout made for product"
+ licenseToken.getProductName()
+ " with status: "
+ licenseToken.getStatus());
// Release the license
LicenseReleaseResult releaseResult
= example.releaseLicense(licenseToken);
if (releaseResult != null) {
System.out.println(
"License release made for product"
+ releaseResult.getProductName()
+ " with status: "
+ (releaseResult.getErrorCode() == null
? " success"
: releaseResult.getErrorCode()));
}
// Example end
System.out.println("All done, bye bye...");
}

Initialize the client application to use the 10Duke Scale SDK. The following example shows how to set up a client application to use the OAuth PKCE flow to sign in users. The example uses the DesktopAppClientBuilder client application builder utility.

private void initClientApp() throws IOException, URISyntaxException {
String configResource = "tenduke.conf";
String userAgent
= "tenduke-scale-sdk-java-cli-example/1.0.0";
clientBuilder = new DesktopAppClientBuilder(
configResource,
userAgent);
try {
client = clientBuilder.initForIdentityBasedLicensing();
} catch (SdkException ex) {
Logger
.getLogger("CliLicensingExample")
.log(
Level.SEVERE,
"""
Startup error, tip: check config file
and that APIs can be reached""",
ex);
throw ex;
}
}

This example shows signing in the user using the OAuth PKCE flow. Running the PKCE flow automatically updates the SDK's authorization provider used for setting the HTTP Authorization header for upcoming license checkout calls. See more about application configuration in: set up an application to use the 10Duke Scale SDK for Javaset up an application to use the 10Duke Scale SDK for Java. The call to signin() triggers an asynchronous workflow where the user authenticates via a browser. When the authentication with the identity provider completes, control returns to the authCodeCallback(final String code, final String error) method. The authorization code is then exchanged for an access token. The resulting ID token is used to print a welcome message and serves as the means to authorize calls to the 10Duke Scale licensing API.

private void signin() throws IOException {
// getPkceClient() return a 10Duke client instance
// that has been configured for the client application.
// The following call will trigger user authentication
// using the desktop browser.
lastPkceInfo = client.getPkceClient().authorize(
(url) -> openUrl(url),
(code, err) -> authCodeCallback(code, err),
false, // wait results = false means async call
30); // max wait time
}

Query which licensees the user is associated with.

private void selectLicensee() {
// Describe the different customer associations
// the user has (licensee relationships).
List<Licensee> licensees = client
.getLicenseCheckoutClient()
.describeLicenseConsumerLicensees(
null,
0,
10,
null,
true);
if (licensees.isEmpty()) {
System.out.println(
"""
No licensee relationships found --> \
no available licenses""");
return;
}
selectLicensee(licensees);
}

Query information on available licenses, based on having selected a licensee in the previous step.

private void selectLicense() {
// Describe licenses that the user has access to
// for a selected licensee
availableLicenses = client
.getLicenseCheckoutClient()
.describeLicenseConsumerLicenses(
selectedLicensee.getId(),
// license consumer id == null as the
// API will use our ID-token to determine
// the license consumer
null,
// filter field: no filtering applied
null,
// filter value: no filtering applied
null,
// with metadata == false, just intrested
// in main license information
false,
// offset == 0, means start at
// the beginning
0,
// limit to 25 first licenses
25,
// order ascending by product name
"productName",
true);
selectedLicense = selectLicense(availableLicenses.getLicenses());
}

Check out a license based on selection.

private LicenseToken checkoutLicense() {
long qty = 1;
if (!QuantityDimension.SEATS.equals(
selectedLicense.getQtyDimension())) {
System.out.println(
"Enter quantity to consume,"
+ " mind that license has:"
+ selectedLicense.getQty()
+ " remaining.");
qty = scanner.nextLong();
}
Map<String, String> claims = new HashMap<>();
claims.put("cliHwId", hwId());
LicenseCheckoutArguments checkoutArguments
= LicenseCheckoutArguments
.builder()
.productName(
selectedLicense.getProductName())
.licenseId(
selectedLicense.getId())
.qty(qty)
.qtyDimension(
selectedLicense.getQtyDimension())
.build();
List<LicenseToken> result = client.getLicenseCheckoutClient()
.checkoutLicense(
claims,
Collections.singletonList(
checkoutArguments));
if (result.size() != 1) {
throw ExceptionBuilder.internalError(
"Expecting 1 license token from"
+ " checkout 1 license, got "
+ result.size()
+ " instead");
} else {
return result.get(0);
}
}

Finally, release the license based on the current checkout.

private LicenseReleaseResult releaseLicense(
final LicenseToken licenseToken) {
if (licenseToken.getLeaseId() == null) {
System.out.println(
"Null lease id, nothing to release.");
return null;
}
long qty = 1;
if (!QuantityDimension.SEATS.equals(
selectedLicense.getQtyDimension())) {
System.out.println(
"Enter final quantity that was consumed:");
qty = scanner.nextLong();
}
Map<String, String> claims = new HashMap<>();
LicenseReleaseArguments releaseArguments
= LicenseReleaseArguments
.builder()
.finalUsedQty(qty)
.leaseId(licenseToken.getLeaseId())
.build();
List<LicenseReleaseResult> result = client
.getLicenseCheckoutClient()
.releaseLicense(
claims,
Collections.singletonList(
releaseArguments));
if (result.size() != 1) {
throw ExceptionBuilder.internalError(
"Expecting 1 license release result"
+ " from release 1 license, got "
+ result.size()
+ " instead");
} else {
return result.get(0);
}
}