Skip to content

Support iTwin share keys #12530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 30, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Apps/Sandcastle/gallery/iModel Mesh Export Service.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
const serviceResponse = await fetch("https://api.cesium.com/itwin/token");
const { access_token: token } = await serviceResponse.json();
// Generate a share key for access to an itwin without oauth
// https://developer.bentley.com/apis/access-control-v2/operations/create-itwin-share/
Cesium.ITwinPlatform.defaultShareKey =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpVHdpbklkIjoiNTM1YTI0YTMtOWIyOS00ZTIzLWJiNWQtOWNlZGI1MjRjNzQzIiwiaWQiOiJmZTliOTgyMS0wYWI5LTQ4ZjItYmMzOC01NjQ5MTg5MDQyOTEiLCJleHAiOjE3NDQ5OTA2MjZ9.7bQIX32CGE4slLDPkOQZ4rRr4BqBSjbUOFAUvcdrFXE";

Cesium.ITwinPlatform.defaultAccessToken = token;
// If you do use oauth for user login set:
// Cesium.ITwinPlatform.defaultAccessToken = 'your token'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jjspace Do we need to have this here? Perhaps an update can point to the tutorial and developer documentation when ready. Same comment for the other sandcastle too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shehzan10 We can certainly add more docs or tutorials later but I don't think there's an issue with providing this information in multiple locations. These sandcastles may end up being one of the main ways to quick reference how to interact with iTwin data in CesiumJS. I think it's worth keeping these 2 lines just for convenience/reference

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jjspace With the goal being to introduce this to the Cesium community via CesiumJS first, IMO its important to keep it simple. By having the comment If you do use oauth for user login set, the statement is vauge-ish because OAuth login has a very specific meaning. So either:

  1. We remove this comment and instead point to the tutorials/documentation when ready.
  2. Make the comment more accurate, yet simple to understand and allows the developer on which approach to use.

Open to other suggestions as well.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @jjspace here, I think its nice to show users that they're able to use either form of authentication to use the sandcastle demo -- sharing or oauth. If you'd like to add more detail to suggesting oauth, you could point them do our oauth docs: https://developer.bentley.com/apis/overview/authorization/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to then have a comment like:

// For alternative forms of authentication you can use, visit https://developer.bentley.com/apis/overview/authorization/.

And combine this with line 35?


// Set up viewer
const viewer = new Cesium.Viewer("cesiumContainer", {
Expand Down
9 changes: 6 additions & 3 deletions Apps/Sandcastle/gallery/iTwin Feature Service.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
const serviceResponse = await fetch("https://api.cesium.com/itwin/token");
const { access_token: token } = await serviceResponse.json();
// Generate a share key for access to an itwin without oauth
// https://developer.bentley.com/apis/access-control-v2/operations/create-itwin-share/
Cesium.ITwinPlatform.defaultShareKey =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpVHdpbklkIjoiMDRiYTcyNWYtZjNjMC00ZjMwLTgwMTQtYTQ0ODhjYmQ2MTJkIiwiaWQiOiI3ZDI2MTQ2YS1mMWE4LTRmYzYtODI5Ny05ODA4ZGRlZDdkOTciLCJleHAiOjE3NDQ5OTAzNzl9.w8B077BL6TJ8Ohit9Pzyw8DlqicKDVBMwgcE0ujLOBQ";

Cesium.ITwinPlatform.defaultAccessToken = token;
// If you do use oauth for user login set:
// Cesium.ITwinPlatform.defaultAccessToken = 'your token'

const iTwinId = "04ba725f-f3c0-4f30-8014-a4488cbd612d";

Expand Down
69 changes: 60 additions & 9 deletions packages/engine/Source/Core/ITwinPlatform.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,48 @@ ITwinPlatform.RealityDataType = Object.freeze({
/**
* Gets or sets the default iTwin access token. This token should have the <code>itwin-platform</code> scope.
*
* This value will be ignored if {@link ITwinPlatform.defaultShareKey} is defined.
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @type {string|undefined}
*/
ITwinPlatform.defaultAccessToken = undefined;

/**
* Gets or sets the default iTwin share key. If this value is provided it will override {@link ITwinPlatform.defaultAccessToken} in all requests.
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @type {string|undefined}
*/
ITwinPlatform.defaultShareKey = undefined;

/**
* Create the necessary Authorization header based on which key/token is set.
* If the {@link ITwinPlatform.defaultShareKey} is set it takes precedence and
* will be used regardless if the {@link ITwinPlatform.defaultAccessToken} is set
* @private
* @returns {string} full auth header with basic/bearer method
*/
ITwinPlatform._getAuthorizationHeader = function () {
//>>includeStart('debug', pragmas.debug);
if (
!defined(ITwinPlatform.defaultAccessToken) &&
!defined(ITwinPlatform.defaultShareKey)
) {
throw new DeveloperError(
"Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey first",
);
}
//>>includeEnd('debug');

if (defined(ITwinPlatform.defaultShareKey)) {
return `Basic ${ITwinPlatform.defaultShareKey}`;
}
return `Bearer ${ITwinPlatform.defaultAccessToken}`;
};

/**
* Gets or sets the default iTwin API endpoint.
*
Expand Down Expand Up @@ -121,15 +157,20 @@ ITwinPlatform.apiEndpoint = new Resource({
ITwinPlatform.getExports = async function (iModelId) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("iModelId", iModelId);
if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwinPlatform.defaultAccessToken first");
if (
!defined(ITwinPlatform.defaultAccessToken) &&
!defined(ITwinPlatform.defaultShareKey)
) {
throw new DeveloperError(
"Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey first",
);
}
//>>includeEnd('debug')

const resource = new Resource({
url: `${ITwinPlatform.apiEndpoint}mesh-export`,
headers: {
Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
Authorization: ITwinPlatform._getAuthorizationHeader(),
Accept: "application/vnd.bentley.itwin-platform.v1+json",
Prefer: "return=representation",
},
Expand Down Expand Up @@ -213,15 +254,20 @@ ITwinPlatform.getRealityDataMetadata = async function (iTwinId, realityDataId) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("iTwinId", iTwinId);
Check.typeOf.string("realityDataId", realityDataId);
if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwinPlatform.defaultAccessToken first");
if (
!defined(ITwinPlatform.defaultAccessToken) &&
!defined(ITwinPlatform.defaultShareKey)
) {
throw new DeveloperError(
"Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey first",
);
}
//>>includeEnd('debug')

const resource = new Resource({
url: `${ITwinPlatform.apiEndpoint}reality-management/reality-data/${realityDataId}`,
headers: {
Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
Authorization: ITwinPlatform._getAuthorizationHeader(),
Accept: "application/vnd.bentley.itwin-platform.v1+json",
},
queryParameters: { iTwinId: iTwinId },
Expand Down Expand Up @@ -275,15 +321,20 @@ ITwinPlatform.getRealityDataURL = async function (
Check.typeOf.string("iTwinId", iTwinId);
Check.typeOf.string("realityDataId", realityDataId);
Check.typeOf.string("rootDocument", rootDocument);
if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwinPlatform.defaultAccessToken first");
if (
!defined(ITwinPlatform.defaultAccessToken) &&
!defined(ITwinPlatform.defaultShareKey)
) {
throw new DeveloperError(
"Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey first",
);
}
//>>includeEnd('debug')

const resource = new Resource({
url: `${ITwinPlatform.apiEndpoint}reality-management/reality-data/${realityDataId}/readaccess`,
headers: {
Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
Authorization: ITwinPlatform._getAuthorizationHeader(),
Accept: "application/vnd.bentley.itwin-platform.v1+json",
},
queryParameters: { iTwinId: iTwinId },
Expand Down
12 changes: 9 additions & 3 deletions packages/engine/Source/Scene/ITwinData.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ ITwinData.createDataSourceForRealityDataId = async function (
realityDataId,
type,
rootDocument,
// TODO: is there a nice-ish way to thread through custom access tokens per asset?
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("iTwinId", iTwinId);
Expand Down Expand Up @@ -227,8 +228,13 @@ ITwinData.loadGeospatialFeatures = async function (
Check.typeOf.number.lessThanOrEquals("limit", limit, 10000);
Check.typeOf.number.greaterThanOrEquals("limit", limit, 1);
}
if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwinPlatform.defaultAccessToken first");
if (
!defined(ITwinPlatform.defaultAccessToken) &&
!defined(ITwinPlatform.defaultShareKey)
) {
throw new DeveloperError(
"Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey first",
);
}
//>>includeEnd('debug')

Expand All @@ -239,7 +245,7 @@ ITwinData.loadGeospatialFeatures = async function (
const resource = new Resource({
url: tilesetUrl,
headers: {
Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
Authorization: ITwinPlatform._getAuthorizationHeader(),
Accept: "application/vnd.bentley.itwin-platform.v1+json",
},
queryParameters: {
Expand Down
51 changes: 45 additions & 6 deletions packages/engine/Specs/Core/ITwinPlatformSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,50 @@ import {

describe("ITwinPlatform", () => {
let previousAccessToken;
let previousShareKey;
beforeEach(() => {
previousAccessToken = ITwinPlatform.defaultAccessToken;
ITwinPlatform.defaultAccessToken = "default-access-token";
previousShareKey = ITwinPlatform.defaultShareKey;
ITwinPlatform.defaultShareKey = undefined;
});

afterEach(() => {
ITwinPlatform.defaultAccessToken = previousAccessToken;
ITwinPlatform.defaultShareKey = previousShareKey;
});

describe("_getAuthorizationHeader", () => {
it("rejects with no default access token or default share key set", async () => {
ITwinPlatform.defaultAccessToken = undefined;
ITwinPlatform.defaultShareKey = undefined;
expect(() =>
ITwinPlatform._getAuthorizationHeader(),
).toThrowDeveloperError(
/Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey/,
);
});

it("uses default access token if default share key not set", () => {
ITwinPlatform.defaultAccessToken = "access-token";
ITwinPlatform.defaultShareKey = undefined;
const header = ITwinPlatform._getAuthorizationHeader();
expect(header).toEqual("Bearer access-token");
});

it("uses default share key if default access token not set", () => {
ITwinPlatform.defaultAccessToken = undefined;
ITwinPlatform.defaultShareKey = "share-key";
const header = ITwinPlatform._getAuthorizationHeader();
expect(header).toEqual("Basic share-key");
});

it("uses default share key even if default access token is set", () => {
ITwinPlatform.defaultAccessToken = "access-token";
ITwinPlatform.defaultShareKey = "share-key";
const header = ITwinPlatform._getAuthorizationHeader();
expect(header).toEqual("Basic share-key");
});
});

describe("getExports", () => {
Expand All @@ -30,12 +67,13 @@ describe("ITwinPlatform", () => {
);
});

it("rejects with no default access token set", async () => {
it("rejects with no default access token or default share key set", async () => {
ITwinPlatform.defaultAccessToken = undefined;
ITwinPlatform.defaultShareKey = undefined;
await expectAsync(
ITwinPlatform.getExports("imodel-id-1"),
).toBeRejectedWithDeveloperError(
"Must set ITwinPlatform.defaultAccessToken first",
/Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey/,
);
});

Expand Down Expand Up @@ -142,12 +180,13 @@ describe("ITwinPlatform", () => {
);
});

it("rejects with no default access token set", async () => {
it("rejects with no default access token or default share key set", async () => {
ITwinPlatform.defaultAccessToken = undefined;
ITwinPlatform.defaultShareKey = undefined;
await expectAsync(
ITwinPlatform.getRealityDataMetadata("itwin-id-1", "reality-data-id-1"),
).toBeRejectedWithDeveloperError(
"Must set ITwinPlatform.defaultAccessToken first",
/Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey/,
);
});

Expand Down Expand Up @@ -271,7 +310,7 @@ describe("ITwinPlatform", () => {
);
});

it("rejects with no default access token set", async () => {
it("rejects with no default access token or default share key set", async () => {
ITwinPlatform.defaultAccessToken = undefined;
await expectAsync(
ITwinPlatform.getRealityDataURL(
Expand All @@ -280,7 +319,7 @@ describe("ITwinPlatform", () => {
"root/document/path.json",
),
).toBeRejectedWithDeveloperError(
"Must set ITwinPlatform.defaultAccessToken first",
/Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey/,
);
});

Expand Down
5 changes: 3 additions & 2 deletions packages/engine/Specs/Scene/ITwinDataSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,12 +391,13 @@ describe("ITwinData", () => {
);
});

it("rejects with no default access token set", async () => {
it("rejects with no default access token or default share key set", async () => {
ITwinPlatform.defaultAccessToken = undefined;
ITwinPlatform.defaultShareKey = undefined;
await expectAsync(
ITwinData.loadGeospatialFeatures("itwin-id-1", "collection-id-1"),
).toBeRejectedWithDeveloperError(
"Must set ITwinPlatform.defaultAccessToken first",
/Must set ITwinPlatform.defaultAccessToken or ITwinPlatform.defaultShareKey/,
);
});

Expand Down