# Auth-only dialog experience

A breakdown of the calls involved with the Auth-only dialog experience

## Notes on Auth dialog URL and region

When initiating the Auth dialog for your End Users, you will need to compose the Auth dialog URL as such
`https://embedded.tray.io/external/auth/create/{embeddedId}/{solutionInstanceId}/{externalAuthId}?code={authorizationCode}`
This URL will vary depending on your region.
If you are in the EU region, the URL would be:
`https://embedded.eu1.tray.io/external/auth/create/{embeddedId}/{solutionInstanceId}/{externalAuthId}?code={authorizationCode}`
If you are in the APAC region, the URL would be:
`https://embedded.ap1.tray.io/external/auth/create/{embeddedId}/{solutionInstanceId}/{externalAuthId}?code={authorizationCode}`

> **Info:** Auth-only dialog requires a solution that does not have any config slots. Please hardcode the config values in your solution if you want to test this pathway.

## Register a user

![register-user-in-your-app](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/494c0e1c-6b42885f_register-user-in-your-app.png)

### User journey map

![createExternalUser : user journey diagram](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/5KPMrHvahx2baWgg8cgakO_integrationwalkthrough1.drawio%20%283%29.png)

### createExternalUser API mutation

The first thing to do in your application is to register a user with [createExternalUser (master token)](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user):

```graphql
 mutation {
  createExternalUser(input: \{name: "Billy Bluehat", externalUserId: "96xxxxxxd7"\}) {
   userId
  }
 }
```

This will return the **userId** in the result:

```json
{
 "data": {
  "createExternalUser": {
   "userId": "fbb96559-xxxx-xxxx-xxxx-5552c2d2fca4"
  }
 }
}
```

### createExternalUser API mutation sample code

The accordion below shows the function and the API code for this call.

### useJWT router function

This is the entry point to the application. In the marketplace app, the [utils/hooks.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/hooks.js#L57) file shows this function in use:

```javascript
export function useJWT() {
    // console.log('useUser', token, user)
    const router = useRouter()
    const { jwt } = router.query
    if (!jwt && router.isReady) \{
        // If there is now json web token present, throw an error
        throw Error('Invalid User Session!')
      \}    
    const \{ data, error, isValidating \} = useSWRImmutable(jwt ? /api/jwt?jwt=${encodeURIComponent(jwt)} : null, fetcher, { shouldRetryOnError: false })
    if (error) \{
        throw error;
    \}    
    return {
        jwt,
        user: \{
            user: get(data, 'user'),
            userToken: get(data, 'userToken'),
        \},
        claims: get(data, 'claims'),
        isLoading: !error && !data,
        isValidating
    }
}
```

From here the control is passed to JWT handler function in the next step.

### JWT handler function code

In the marketplace app, the [pages/api/jwt.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/jwt.js#L6) file shows this function code in use:

```javascript
export default async function handler(req, res) {
    try {
        const { jwt: queryJWT } = req.query;
        const masterToken = await getMasterToken(req.query);
        let \{ user, claims \} = await validateJsonWebToken(masterToken, queryJWT);
        if (claims.expires) {
            const expires = new Date(claims.expires);
            const now = new Date();
            if (now.getTime() > expires.getTime()) {
                throw Error('Token has expired')
            }
        }
        if (!user) \{
            console.log('Could not find user with id', claims.id, 'creating one')
            await mutations.createUser(claims.id, claims.name, masterToken)
            user = await queries.getUser(claims.id, masterToken);
        \} else {
            // TODO: update users name from claim
        }
        const userToken = await mutations.createUserToken(user.id, masterToken);
        res.status(200).json(\{ user, userToken, claims \})
    } catch (e) {
        console.error(e);
        res.status(400).json({ message: e.message })
    }
}
```

### createExternalUser API mutation code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L193) file shows this API mutation in use:

```javascript
createUser: async (externalUserId, name, token = process.env.MASTER_TOKEN) => {
        return request(token, gql
            mutation createExternalUser($externalUserId: String!, $name: String!) {
                createExternalUser(input:\{ externalUserId: $externalUserId, name: $name \}) {
                    userId
                }
            }
        , \{
            externalUserId,
            name
        \}).then(data => get(data, 'createExternalUser.userId'))
    },
```

### Authorize API mutation

The **userId** can then be used to create an **accessToken** with [authorize (master token)](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user-token):

```graphql
mutation {
 authorize(input: {userId: "fbb96559-xxxx-xxxx-xxxx-5552c2d2fca4"}) {
  accessToken
 }
}
```

Result:

```json
{
 "data": {
  "authorize": {
   "accessToken": "0c8c8e16e39e4xxxxxxxxxxxxxxxxxxxxxx49545b584e307063710d1ee"
  }
 }
}
```

### Authorize API mutation sample code

The accordion below shows the function and the API code for this call.

### JWT handler function code

In the marketplace app the [pages/api/jwt.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/jwt.js#L6) file shows that once a user is created we make the function call to generate a userToken:

```javascript
export default async function handler(req, res) {
    try {
        const { jwt: queryJWT } = req.query;
        const masterToken = await getMasterToken(req.query);
        let \{ user, claims \} = await validateJsonWebToken(masterToken, queryJWT);
        if (claims.expires) {
            const expires = new Date(claims.expires);
            const now = new Date();
            if (now.getTime() > expires.getTime()) {
                throw Error('Token has expired')
            }
        }
        if (!user) \{
            console.log('Could not find user with id', claims.id, 'creating one')
            await mutations.createUser(claims.id, claims.name, masterToken)
            user = await queries.getUser(claims.id, masterToken);
        \} else {
            // TODO: update users name from claim
        }
        const userToken = await mutations.createUserToken(user.id, masterToken);
        res.status(200).json(\{ user, userToken, claims \})
    } catch (e) {
        console.error(e);
        res.status(400).json({ message: e.message })
    }
}
```

### authorize API mutation code

In the demo app the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L206) file shows the graphql mutation in use:

```javascript
createUserToken: async (userId, token = process.env.MASTER_TOKEN) => {
        return request(token, gql
            mutation authorize($userId: ID!) {
                authorize(input:{ userId: $userId }) {
                    accessToken
                }
            }
        , {
            userId
        }).then(data => get(data, 'authorize.accessToken'))
    },
```

## Log existing user in

![api calls step by step : login-user : image](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/45bUHgUOH9sQIvRLRsjoKr_login-user.png)

### User journey map

![api calls step by step : loginExistingUser : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/5O6CEc3tQyISO9Jhxmx4Zy_integrationwalkthrough1.drawio%20%284%29.png)

### Retrieve existing user sample code

The accordion below shows the function and the API code for this call.

### JWT handler function code

The entry point for login is through the useJWT router function as shown in registering the user, Post that JWT handler function picks up control.
In the marketplace app, the [pages/api/jwt.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/jwt.js#L13) file shows this function code in use:

```javascript
export default async function handler(req, res) {
    try {
        const { jwt: queryJWT } = req.query;
        const masterToken = await getMasterToken(req.query);
        let \{ user, claims \} = await validateJsonWebToken(masterToken, queryJWT);
        if (claims.expires) {
            const expires = new Date(claims.expires);
            const now = new Date();
            if (now.getTime() > expires.getTime()) {
                throw Error('Token has expired')
            }
        }
        if (!user) \{
            console.log('Could not find user with id', claims.id, 'creating one')
            await mutations.createUser(claims.id, claims.name, masterToken)
            user = await queries.getUser(claims.id, masterToken);
        \} else {
            // TODO: update users name from claim
        }
        const userToken = await mutations.createUserToken(user.id, masterToken);
        res.status(200).json(\{ user, userToken, claims \})
    } catch (e) {
        console.error(e);
        res.status(400).json({ message: e.message })
    }
}
```

In the above code, the function call to `validateJsonWebToken` attempts to find the user.

### validateJsonWebToken function code

In the marketplace app the [utils/security.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/security.js#L26) file shows this function code in use:

```javascript
export async function validateJsonWebToken(masterToken, jwtToDecode) {
    if (!masterToken) {
        throw Error('Expecting a valid token query parameter')
    }  
    console.log('got master token for request', masterToken)
    if (!jwtToDecode) {
        throw Error('Expecting a valid jwt query parameter')
    }        
    const claims = jwt.verify(jwtToDecode, masterToken)
    if (!claims.id) {
        throw Error('Expecting a valid user.id JWT claim')
    }
    if (!claims.name) {
        throw Error('Expecting a valid user.name JWT claim')
    }    
    console.log('getting user for id', claims.id)
    let user = await queries.getUser(claims.id, masterToken);
    console.log('queried user', user)
    return \{
        user,
        claims
    \};
}
```

In the code above, the `verify` function decodes the JWT token and extracts the `id`, `name`, `expires`, `tagFilters` value from it. We then use the `id` to query the user using getUser function.

### getUser API query sample code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L171) file shows this function code in use:

```javascript
getUser: async (externalUserId, token = process.env.MASTER_TOKEN) => {
        return request(token, gql
        query getUser($externalUserId: String!) {
            users(criteria:{ externalUserId: $externalUserId}) {
            edges {
              node {
                id
                name
                externalUserId
              }
            }
          }
        }
        , {
            externalUserId
        }).then(data => get(data, 'users.edges[0].node'))
    }
```

> **Info:** The best practice here would be to pull the end user information from your own database. You can do it by using the id (email, username etc.) that end users use to log in to your marketplace as their external ID and create a Tray user. Store this External ID that you used in the [createExternalUser](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user) API call and the returned user ID in your database.
> This way, you wouldn't have to make this extra graphql query call. We have not followed the best practice in the marketplace app to show how you can achieve this with the API call.

## List available integrations

![list-available-solutions](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/494c0e1c-fed61651_listSolutions.png)

### User journey map

![api calls step by step : listSolutions : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/6UACqf9IzRAsmxTcgIuc5P_integrationwalkthrough1.drawio%20%285%29.png)

### solutions API query

The Solutions available to your End Users can be listed using the [solutions query (master token)](https://tray.ai/documentation/developer/embedded-apis/solutions#graphql-get-solutions):

```graphql
query {
  viewer {
   solutions {
    edges {
     node {
      id
      title
      description
      tags
      customFields {
       key
       value
      }
     }
     cursor
    }
    pageInfo {
      hasPreviousPage
      hasNextPage
      startCursor
      endCursor
    }
   }
  }
 }
```

Within the **node** of the result you will find the Solution **id**:

```json
{
 "data": {
  "viewer": {
   "solutions": {
    "edges": [
     {
      "node": {
       "id": "91c0f56f-xxxx-xxxx-xxxx-184686978ea5",
       "title": "Webhook demo",
       "description": "",
       "tags": [],
       "customFields": [],
       "configSlots": [
        \{
         "externalId": "external_slack-channel",
         "title": "slack-channel",
         "defaultValue": "\"CxxxxTP\""
        \},
        \{
         "externalId": "external_slack-message",
         "title": "slack-message",
         "defaultValue": "\"The webhook for your solution instance has been triggered\""
        \}
       ]
      },
      "cursor": "OTFjMGY1NmYtMGE1MC00M2Y4LWExMTAtMTg0Njg2OTc4ZWE1"
     }
    ],
    "pageInfo": \{
     "hasNextPage": false,
     "endCursor": "OTFjMGY1NmYtMGE1MC00M2Y4LWExMTAtMTg0Njg2OTc4ZWE1",
     "hasPreviousPage": false,
     "startCursor": "OTFjMGY1NmYtMGE1MC00M2Y4LWExMTAtMTg0Njg2OTc4ZWE1"
    \}
   }
  }
 }
}
```

Note that the above pageInfo values can be used for [pagination](https://developer.tray.io/developer/getting-started/implementation-notes/pagination)

### solutions API query sample code

The accordion below shows the function and the API code for this call.

### Solutions handler function code

In the marketplace app, the [pages/api/solutions.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/solutions.js#L5) file shows this function code in use:

```javascript
export default async function handler(req, res) {
  try {
    const \{ token: userToken, tags \} = req.query;
    if (!userToken) {
      throw Error('Expecting a valid token parameter')
    }
    const tagsToFilter = tags ? tags.split(',') : [];
    const solutions = await queries.solutions(userToken, tagsToFilter);
    res.status(200).json(\{ solutions: map(solutions, e => e.node) \})
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### Solutions query code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L17) file shows this API query in use:

```javascript
solutions: async (token = process.env.MASTER_TOKEN, tags = []) => {
        return request(token, gql
        query solutions($tags: [String!]) {
            viewer {
              solutions(criteria: { tags: $tags}) {
                edges {
                  node {
                    id
                    title
                    description
                    tags
                    customFields {
                      key
                      value
                    }
                  }
                  cursor
                }
                pageInfo {
                  hasNextPage
                  hasPreviousPage
                  startCursor
                  endCursor
                }
              }
            }
          }          
        , {
          tags
        }).then(data => get(data, 'viewer.solutions.edges'))
    },
```

## User configures integration

![authOnlyFigma](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/7DtFSM3okIAgfbAZgLmSnR_authOnlyFigma.png)
You could have one connect button which creates the solutions instance and one authorize button for each service in the integration as shown above. Follow these steps to present this experience for end users:

### Create solution instance

### User journey map

![api calls step by step : createSolutionInstance : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/oofdHt0VCnjznt8aom7ig_createSolutionInstance.png)

### createSolutionInstance API mutation

With the correct **solutionId** from above use [createSolutionInstance (user token)](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-create-solution-instance) to create a Solution Instance:

```graphql
mutation {
 createSolutionInstance(input: \{
   solutionId: "e2ed06d9-xxxx-xxxx-xxxx-218ae63a30b6",
   instanceName: "Webhook demo"
 \}) {
  solutionInstance {
   id
   name
   enabled
   created
  }
 }
}
```

The result will return the id of the Solution Instance:

```json
{
 "data": {
  "createSolutionInstance": {
   "solutionInstance": {
    "id": "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d",
    "name": "Webhook demo",
    "enabled": false,
    "created": "2020-10-02T13:06:41.605Z",
    "solution": \{
     "id": "91c0f56f-xxxx-xxxx-xxxx-184686978ea5",
     "title": "Webhook demo",
     "description": ""
    \},
    "authValues": [],
    "configValues": [],
    "workflows": {
     "edges": [
      {
       "node": \{
        "id": "e79e7a66-xxxx-xxxx-xxxx-16b67f870718",
        "sourceWorkflowId": "804c5f50-9e7d-4b50-858b-9338c507ff45",
        "sourceWorkflowName": "Webhook demo",
        "triggerUrl": "https://0ed9f5b2-xxxx-xxxx-xxxx-44ab9507e4ae.trayapp.io"
       \}
      }
     ]
    },
    "solutionVersionFlags": \{
     "hasNewerVersion": false,
     "requiresUserInputToUpdateVersion": false,
     "requiresSystemInputToUpdateVersion": false
    \}
   }
  }
 }
}
```

### createSolutionInstance API mutation sample code

The accordion below shows the function and the API code for this call.

### connectSolution function code breakdown

In the marketplace app, the [utils/tray.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L17) file shows the `connectSolution` function. Here's a breakdown of this function:
![connectSolution code breakdown](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/1nW34JN2QBhUzCi4QPAcI2_connectSolution%20code%20breakdown.png)

### connectSolution function code

In the marketplace app, the [utils/tray.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L17) file shows the `connectSolution` function in use:

```javascript
export const connectSolution = async (user, jwt, solution, config, reload) => {
    return new Promise(async (resolve, reject) => {
        const disableAutoEnable = ['yes', 'true'].indexOf(get(solution, 'customFieldsMap.disableautoenable')) != -1;
        const newInstance = await axios.post(/api/create-instance, {
            userToken: user.userToken,
            solutionId: solution.id,
            instanceName: My instnace of ${solution.id}
        }).then(r => r.data).catch(e => { throw Error(Error creating solution instance: $\{get(e, 'response.data.message')\}) })

        // Create an auth code
        const { code: authorizationCode } = await axios.get(/api/code, { params: { jwt } }).then(r => r.data).catch(e => { throw Error(Error getting authorization code: $\{get(e, 'response.data.message')\}) })

        openConfigWizard(
            https://${config.get('settings.wizardDomain')}/external/solutions/${config.get('settings.partnerId')}/configure/${newInstance.id}?code=${authorizationCode},
            async () => {
                console.log('wizard success')
                // on started, set loading etc
                if (!disableAutoEnable) {
                    // If disable auto enable is false, 
                    // we want to make an enable instance call
                    await toggleInstance(user, { id: newInstance.id }, true)
                }
                reload && reload(newInstance);
                resolve();
            },
            async () => {
                console.log('wizard error')
                // on error, delete
                await axios.post(/api/delete-instance, \{
                    userToken: user.userToken,
                    solutionInstanceId: newInstance.id,
                \}).then(r => r.data).catch(e => { throw Error(Error cleaning up solution instance: $\{get(e, 'response.data.message')\}) })
                console.log('deleted instance')
                reload && reload();
                reject(Error(config.get('content.connectCloseErrorMessage')));
            },
            config.get('settings.windowMode')
        );
    })
}
```

### createSolutionInstance handler code

In the marketplace app, the [pages/api/create-instance.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/create-instance.js#L3) file shows this function code in use:

```javascript
export default async function handler(req, res) {
  try {
    const \{ userToken, solutionId, instanceName, authValues, configValues \} = req.body;
    const instance = await mutations.createSolutionInstance(userToken, solutionId, instanceName, authValues, configValues);
    res.status(200).json(instance)
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### &#x20;createSolutionInstance mutation sample code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L230) file shows this API mutation in use:

```javascript
createSolutionInstance: async (userToken, solutionId, name, authValues = [], configValues = []) => {
      return request(userToken, gql
            mutation create($solutionId: ID!, $name: String!, $authValues: [AuthValue!]!, $configValues: [ConfigValue!]!) {
              createSolutionInstance(input: \{solutionId: $solutionId, instanceName: $name, authValues: $authValues, configValues: $configValues\}) {
                solutionInstance {
                  id
                  name
                  enabled
                  solution {
                    id
                    title
                    description
                    tags
                    customFields {
                      key
                      value
                    }
                  }
                  solutionVersionFlags {
                    hasNewerVersion
                    requiresUserInputToUpdateVersion
                  }
                }
              }
            }
        , \{
          solutionId,
          name,
          authValues,
          configValues,
      \}).then(data => get(data, 'createSolutionInstance.solutionInstance'))
    }
```

### Initialize auth only dialog

### User journey map

![authOnlyDialog configures userJourney](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/1I0morbomczMqtDMlVExmm_authOnlyDialog_userJourney.png)

### 1 - Create authorization code

You will need to use [generateAuthorizationCode (using master token)](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-config-wizard-auth-code) to get the one-time auth-only code for the End User.

### generateAuthorizationCode API mutation

```graphql
mutation {
 generateAuthorizationCode(input: {userId: "fbb96559-xxxx-xxxx-xxxx-5552c2d2fca4"}) {
  authorizationCode
  clientMutationId
 }
}
```

This will return **a one-time use code** which can be used in the **Auth dialog url**:

```json
{
 "data": {
  "generateAuthorizationCode": \{
   "authorizationCode": "b8aab26cxxxxxxxxxxxxxxxxxxxx776c7bba7977",
   "clientMutationId": null
  \}
 }
}
```

### 2 - Compose the Auth dialog URL

Now you can compose the Auth dialog popup URL using the returned **solutionInstanceId** (createSolutionInstance mutation) and \*\*authorizationCode \*\*(generateAuthorizationCode mutation) from the calls above:
`https://embedded.tray.io/external/auth/create/{embeddedId}/{solutionInstanceId}/{externalAuthId}?code={authorizationCode}`
`externalAuthId` is the External ID of the auth in your solution.
![set-external-id](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/fd0bc8dd-2e5f4f24_authOnlyDialog.png)
Remember that [embeddedId](https://tray.ai/documentation/platform/embedded/overview/preparing-your-environment/#embedded-id) is the value that you set when configuring your [Config Wizard CSS](https://tray.ai/documentation/platform/embedded/advanced-features/whitelabelling/config-wizard).
Note that your exact URL may differ if you are in a non-US region. Please see the [note above](#notes-on-auth-dialog-url-and-region) for more details.

### Open the URL in browser

![authOnlyDialog ss](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/3wxG4ksavHz3HV2zsN8Avl_authOnlyDialog_ss.png)

### Fill the auth data

Enter the auth data for the End User.

### 3 - Update Solution Instance with Authorization

Final step is to use the [Update Solution Instance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to update the **solutionInstanceId** with authValues for the End User (this requires an access token for your End User as per [Create User Token](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user-token))

### updateSolutionInstance API mutation

Use the [updateSolutionInstance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to set `enabled` to 'true'

```js
mutation {
  updateSolutionInstance(
    input: {
      solutionInstanceId: "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d"
      instanceName: "Webhook demo"
      authValues: [
        {
          externalId: "external_slack_authenticaion"
          value: "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
        }
      ]
      enabled: true
    }
  ) {
    solutionInstance {
      id
      name
      enabled
      created
      authValues {
        externalId
        authId
      }
    }
  }
}
```

The returned data will confirm that the Solution Instance contains the authValues:

```json
{
 "data": {
  "updateSolutionInstance": {
   "solutionInstance": {
    "id": "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d",
    "name": "Webhook demo",
    "enabled": true,
    "created": "2020-10-02T13:06:41.605Z",
    "authValues": [
     \{
      "externalId": "external_slack_authentication",
      "authId": "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
     \}
    ]
   }
  }
 }
}
```

Note that these steps would be repeated for each service auth that the end user provides:

> **Info:** You could also attach all auths in one go through a updateSolutionInstance mutation instead of doing one at a time. In that case, you will have to persist the authValues somewhere till the end user has provided auth for all required services.

### Enable the Solution Instance

Navigate to the relevant **Project** > **Solution instances** section. Here, you'll find the Solution Instance for that End User. Currently, it's disabled to allow you to perform any necessary checks or make changes before activating the Instance.
![ui-instance-disabled](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/494c0e1c-b58a0ac1_enable-solution-instance%20%281%29.png)

### User journey map

![api calls step by step : updateSolutionInstance : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/1JctYP38YEB9q5ojgHb79H_integrationwalkthrough3.drawio%20%282%29.png)

### updateSolutionInstance API mutation

Use the [updateSolutionInstance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to set `enabled` to 'true'

```js
mutation {
  updateSolutionInstance(
    input: {
      solutionInstanceId: "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d"
      instanceName: "Webhook demo"
      authValues: [
        {
          externalId: "external_slack_authenticaion"
          value: "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
        }
      ]
      enabled: true
    }
  ) {
    solutionInstance {
      id
      name
      enabled
      created
      authValues {
        externalId
        authId
      }
    }
  }
}
```

The returned data will confirm that the Solution Instance contains the authValues:

```json
{
 "data": {
  "updateSolutionInstance": {
   "solutionInstance": {
    "id": "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d",
    "name": "Webhook demo",
    "enabled": true,
    "created": "2020-10-02T13:06:41.605Z",
    "authValues": [
     \{
      "externalId": "external_slack_authentication",
      "authId": "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
     \}
    ]
   }
  }
 }
}
```

### updateSolutionInstance API sample code

The accordion below shows the function and the API code for this call.

### toggleInstance function code

In the marketplace app, the [utils/tray.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L95) file shows this function code in use:

```javascript
export const toggleInstance = async (user, instance, enabled, reload) => {
    return new Promise(async (resolve, reject) => {
        console.log('toggleInstance')
        // on error, delete
        await axios.post(/api/toggle-instance, \{
            userToken: user.userToken,
            solutionInstanceId: instance.id,
            enabled,
        \}).then(r => r.data).catch(e => { throw Error(Error toggling solution instance: $\{get(e, 'response.data.message')\}) })
        reload && reload();
        resolve();
    })
}
```

### toggleInstance handler code

In the marketplace app, the [pages/api/toggle-instance.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/toggle-instance.js#L3) file shows the handler function code in use:

```javascript
export default async function handler(req, res) {
  try {
    const \{ userToken, solutionInstanceId, enabled \} = req.body;
    await mutations.toggleSolutionInstance(userToken, solutionInstanceId, enabled);
    res.status(200).json({ message: 'ok' })
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### toggleSolutionInstance mutation code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L275) file shows this API mutation in use:

```javascript
toggleSolutionInstance: async (userToken, solutionInstanceId, enabled) => {
      return request(userToken, gql
            mutation toggle($solutionInstanceId: ID!, $enabled: Boolean!) {
              updateSolutionInstance(input: \{ solutionInstanceId: $solutionInstanceId, enabled: $enabled \}) {
                solutionInstance {
                  id
                  enabled
                }
              }
            }
        , \{
          solutionInstanceId,
          enabled
      \})
    }
```

Please not the warning below when enabling instances through the API:

> **Warning:** If you are auto-enabling the instances for your end users after they create the solution, your code should be configured to delay this call by at least 2 seconds after Create Solution Instance is complete.

## Run and test

### Trigger a run of the Workflow Instance

Now you can paste the **triggerUrl** into your browser address bar and hit 'enter' to trigger a run of the Workflow Instance.

### Debugging the Solution Instance

Navigate to the relevant **Project** > **Solution instances** you will now see that the Solution Instance is enabled and you can click on '**Debug Workflows**' on the solution instance card as shown below:
![ui-instance-click-debug](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/494c0e1c-965f6f41_Debugging%20the%20Solution%20Instance%20%281%29.png)
This will open a modal of all the linked workflows of this solution instance. You can the debug the workflow that you want.
![debugging insatnce 3](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/4Nynn6le9P8Lis6Bp0yTQQ_debugging%20insatnce%203.png)
This will open a read-only version of the Workflow in a new tab:
![workflow-instance-debug](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/494c0e1c-97b8e80f_workflow-instance-debug.png)
![msg-to-slack-channel](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/494c0e1c-d24dcc0f_msg-to-slack-channel.png)

> **Info:** Please see our documentation on [troubleshooting](https://tray.io/documentation/tray-uac/logs-and-troubleshooting/logs-and-debugging/) for more on debugging, log streaming and error handling.

## List an End User's Solution Instances

![list-users-instances](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/494c0e1c-b4ceecd4_list-users-instances.png)

### User journey map

![listSolutionInstanes : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/1WbywikbiZ8D1Xi3TAohaI_integrationwalkthroughdraft.drawio%20%282%29.png)

### solutionInstances API query

You can pass the **userId** when using the [solutionInstances query (user/master token)](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-get-solution-instances) (if using the master token for this query it is still possible to filter Solution Instances by the owner of the Solution Instance, or by the ID of the Solution which it came from):

```graphql
query ($ownerId: String!){
	viewer {
		solutionInstances(criteria: { owner: $ownerId }) {
			edges {
				node {
					id
					name
					enabled
					owner
					created
					solutionVersionFlags {
						hasNewerVersion
						requiresUserInputToUpdateVersion
						requiresSystemInputToUpdateVersion
					}
					workflows {
						edges {
							node {
								triggerUrl
								id
								sourceWorkflowId
							}
						}
					}
					authValues {
						externalId
						authId
					}
					configValues {
						externalId
						value
					}
				}
				cursor
			}
			pageInfo {
				startCursor
				endCursor
				hasNextPage
				hasPreviousPage
			}
		}
	}
}
```

This will return a list of solution instances.

```javascript
{
  "data": {
    "viewer": {
      "solutionInstances": {
        "edges": [
          {
            "node": {
              "id": "533d48e8-XXXX-XXXX-XXXX-74ff887198d3",
              "name": "SampleApp",
              "enabled": true,
              "owner": "13b3ab9c-XXXX-XXXX-XXXX-c4dd07fbbfa4",
              "created": "2019-12-03T21:11:09.456Z",
              "solutionVersionFlags": \{
                "hasNewerVersion": true,
                "requiresUserInputToUpdateVersion": true,
                "requiresSystemInputToUpdateVersion": false
              \},
              "workflows": {
                "edges": [
                  {
                    "node": \{
                      "triggerUrl": null,
                      "id": "89f8ebeb-XXXX-XXXX-XXXX-be070f3cd0f8",
                      "sourceWorkflowId": "3af6461f-XXXX-XXXX-XXXX-91ada5769f90"
                    \}
                  }
                ]
              },
              "authValues": [
                \{
                  "externalId": "external_slack_authentication",
                  "authId": "dcbc11f1-XXXX-XXXX-XXXX-b7f90dafcf28"
                \}
              ],
              "configValues": []
            },
            "cursor": "NTMzZDQ4ZTgtNjM2Zi00M2FkLTk1ZjItNzRmZjg4NzE5OGQz"
          },
          {
            "node": {
              "id": "fa34cb4c-XXXX-XXXX-XXXX-71205a990ab6",
              "name": "SampleApp",
              "enabled": false,
              "owner": "13b3ab9c-XXXX-XXXX-XXXX-c4dd07fbbfa4",
              "created": "2019-12-03T21:15:55.005Z",
              "solutionVersionFlags": \{
                "hasNewerVersion": true,
                "requiresUserInputToUpdateVersion": true,
                "requiresSystemInputToUpdateVersion": false
              \},
              "workflows": {
                "edges": [
                  {
                    "node": \{
                      "triggerUrl": null,
                      "id": "5a89642e-XXXX-XXXX-XXXX-ddb9daca26b3",
                      "sourceWorkflowId": "3af6461f-XXXX-XXXX-XXXX-91ada5769f90"
                    \}
                  }
                ]
              },
              "authValues": [],
              "configValues": []
            },
            "cursor": "ZmEzNGNiNGMtYjdlMS00NjgxLTliZmUtNzEyMDVhOTkwYWI2"
          }
        ],
        "pageInfo": \{
          "startCursor": "NTMzZDQ4ZTgtNjM2Zi00M2FkLTk1ZjItNzRmZjg4NzE5OGQz",
          "endCursor": "ZmEzNGNiNGMtYjdlMS00NjgxLTliZmUtNzEyMDVhOTkwYWI2",
          "hasNextPage": false,
          "hasPreviousPage": false
        \}
      }
    }
  }
}
```

### solutionInstances API sample code

The accordion below shows the function and the API code for this call.

### listSolutionInstances handler function code

In the marketplace app, the [pages/api/instances.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/instances.js#L5) file shows this function code in use:

```javascript
export default async function handler(req, res) {
  try {
    const { token: userToken } = req.query;
    if (!userToken) {
      throw Error('Expecting a valid token parameter')
    }
    const solutionInstances = await queries.solutionInstances(userToken);
    res.status(200).json(\{ instances: map(solutionInstances, e => e.node) \})
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### Solution instances query code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L86) file shows this API query in use:

```javascript
solutionInstances: async (token) => {
        return request(token, gql
        query solutionInstances {
            viewer {
              solutionInstances {
                edges {
                  node {
                    id
                    name
                    enabled
                    solution {
                      id
                      title
                      description
                      tags
                      customFields {
                        key
                        value
                      }
                    }
                    solutionVersionFlags {
                      hasNewerVersion
                      requiresUserInputToUpdateVersion
                    }
                  }
                  cursor
                }
                pageInfo {
                  hasNextPage
                  hasPreviousPage
                  startCursor
                  endCursor
                }
              }
            }
        }
        ).then(data => get(data, 'viewer.solutionInstances.edges'))
    }
```

### Important note on listing solution instances

> **Info:** The best practice here would be to grab the Solution Instance IDs and Workflow IDs from the createSolutionInstance call. You can do it when users configure the integration the very first time and then store these IDs in your database with the corresponding user.
> This way, you wouldn't have to make this extra graphql query call. We have not followed the best practice in the marketplace app to show how you can achieve this with the API call.

## User edits integration

![editsIntegration authOnly](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/1q7672WE8OLr7H6Gja1rGe_editsIntegration_authOnly.png)
Follow these steps to present this experience for end users:

### User journey map

![authOnlyDialog edits userJourney](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/75NXbrZ0i9LiuDGSCY8NyU_authOnlyDialog_edits_userJourney.png)

### 1 - Create authorization code

You will need to use [generateAuthorizationCode (using master token)](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-config-wizard-auth-code) to get the one-time auth-only code for the End User.

### generateAuthorizationCode API mutation

```graphql
mutation {
 generateAuthorizationCode(input: {userId: "fbb96559-xxxx-xxxx-xxxx-5552c2d2fca4"}) {
  authorizationCode
  clientMutationId
 }
}
```

This will return **a one-time use code** which can be used in the **Auth dialog url**:

```json
{
 "data": {
  "generateAuthorizationCode": \{
   "authorizationCode": "b8aab26cxxxxxxxxxxxxxxxxxxxx776c7bba7977",
   "clientMutationId": null
  \}
 }
}
```

### 2 - Compose the Auth dialog URL

Now you can compose the Auth dialog popup URL using the returned **solutionInstanceId** (createSolutionInstance mutation) and \*\*authorizationCode \*\*(generateAuthorizationCode mutation) from the calls above:
`https://embedded.tray.io/external/auth/create/{embeddedId}/{solutionInstanceId}/{externalAuthId}?code={authorizationCode}`
`externalAuthId` is the External ID of the auth in your solution.
![set-external-id](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/fd0bc8dd-2e5f4f24_authOnlyDialog.png)
Remember that [embeddedId](https://tray.ai/documentation/platform/embedded/overview/preparing-your-environment/#embedded-id) is the value that you set when configuring your [Config Wizard CSS](https://tray.ai/documentation/platform/embedded/advanced-features/whitelabelling/config-wizard).
Note that your exact URL may differ if you are in a non-US region. Please see the [note above](#notes-on-auth-dialog-url-and-region) for more details.

### Open the URL in browser

![authOnlyDialog ss](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/3wxG4ksavHz3HV2zsN8Avl_authOnlyDialog_ss.png)

### Fill the auth data

Enter the auth data for the End User.

### 3 - Update Solution Instance with Authorization

Final step is to use the [Update Solution Instance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to update the **solutionInstanceId** with authValues for the End User (this requires an access token for your End User as per [Create User Token](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user-token))

### updateSolutionInstance API mutation

Use the [updateSolutionInstance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to set `enabled` to 'true'

```js
mutation {
  updateSolutionInstance(
    input: {
      solutionInstanceId: "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d"
      instanceName: "Webhook demo"
      authValues: [
        {
          externalId: "external_slack_authenticaion"
          value: "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
        }
      ]
      enabled: true
    }
  ) {
    solutionInstance {
      id
      name
      enabled
      created
      authValues {
        externalId
        authId
      }
    }
  }
}
```

The returned data will confirm that the Solution Instance contains the authValues:

```json
{
 "data": {
  "updateSolutionInstance": {
   "solutionInstance": {
    "id": "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d",
    "name": "Webhook demo",
    "enabled": true,
    "created": "2020-10-02T13:06:41.605Z",
    "authValues": [
     \{
      "externalId": "external_slack_authentication",
      "authId": "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
     \}
    ]
   }
  }
 }
}
```

### 4 - Find all solution instances using this service auth and update them

There are several ways you could do this. Here are two of them:

* Searching your backend database: You could store the auth information for every solution instance and when an auth attached with one solution instance changes, you could query your database to find all other solution instances using this auth and update them one by one as shown in step 3.
* Tagging solution instances: You could tag the solution instances with flags (for example: a tag called 'slack auth only' to all solution instances using slack. If the slack auth is updated, you could retrieve all other solution instances with the 'slack auth only' tag and update them one by one as shown in step 3.

Note that the steps above would be repeated for each service auth that the end user provides.

### Note on updating Solution Instances

When **you** (not end user) publish a new version of a **Solution**, there are two types of updates that would happen to all Solution Instances linked to this Solution:

* A 'lazy' update (no new config or auths are required)
* A 'breaking changes' update (i.e. requires new auth and/or config data) whereby you will have to notify End Users to edit their integrations as above
  For more details, please see our guide to Updating Solutions and Instances.

## User deletes integration

![api calls breakdown : deleteInstance : ui image](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/73b66ALFE9kokDLwecDEFF_deleteInstance_apicalls.png)

### Delete solution instance

Users may want to delete the instance they no longer wish to use and you can achieve it by following the steps below:

### User journey map

![api callls breakdown : delete solution instance : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/auth-only-dialog/5ttzaCQhuN4bAmrls3XRBK_integrationwalkthroughdraft.drawio%20%283%29.png)

### deleteSolutionInstance API mutation

You can pass the **solutionId** to [deleteSolutionInstance (user token)](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-delete-solution-instance) to delete a Solution Instance:

```json
mutation ($solutionInstanceId: ID!){
  removeSolutionInstance(input: {solutionInstanceId: $solutionInstanceId}) {
   clientMutationId
 }
}
```

The result will return the clientMutationId:

```json
{
  "data": {
    "removeSolutionInstance": {
      "clientMutationId": "687xxxxxxxxxxx931"
    }
  }
}
```

### deleteSolutionInstance API mutation sample code

The accordion below shows the function and the API code for this call.

### disconnectSolution function code

In the marketplace app, the [utils/tray.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L83) file shows this function code in use:

```js
export const disconnectSolution = async (user, instance, reload) => {
    return new Promise(async (resolve, reject) => {
        // on error, delete
        await axios.post(/api/delete-instance, \{
            userToken: user.userToken,
            solutionInstanceId: instance.id,
        \}).then(r => r.data).catch(e => { throw Error(Error disconnecting solution instance: $\{get(e, 'response.data.message')\}) })
        reload && reload();
        resolve();
    })
}
```

### delete-instance handler function code

In the marketplace app, the [pages/api/delete-instance.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/delete-instance.js#L3) file shows the handler function code in use:

```js
export default async function handler(req, res) {
  try {
    const \{ userToken, solutionInstanceId \} = req.body;
    await mutations.deleteSolutionInstance(userToken, solutionInstanceId);
    res.status(200).json({ message: 'ok' })
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### deleteSolutionInstance mutation sample code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L263) file shows this API mutation in use:

```js
deleteSolutionInstance: async (userToken, solutionInstanceId) => {
      return request(userToken, gql
            mutation delete($solutionInstanceId: ID!) {
              removeSolutionInstance(input: { solutionInstanceId: $solutionInstanceId }) {
                clientMutationId
              }
            }
        , {
          solutionInstanceId
      })
    }
```
