# Authorization

Most of the time, you will need to authenticate your app with a third-party service. This can be done in a few different ways, depending on the service you are integrating with.

## Customize The Login Form

By default, the login page for your application will only require you to enter the `apiToken` to communicate with the third party service. There is a way to customize this:

```js title="index.js"
configuration.projectIntegration.loginForm = {
  fields: [
    // Fields Reference
  ],
  performGetTokenRequest: async (fieldsCredentials) => {
    const getTokenUrl = `${authUrl}/oauth/token?grant_type=client_credentials`;
    const tokenId = base64.encode(`${fieldsCredentials.username}:${fieldsCredentials.password}`);
    const headers = {Authorization: `Bearer ${tokenId}`};

    const clientCredentials = await axios.get(getTokenUrl, { headers });

    return {
      access_token: clientCredentials.data.access_token,
      refresh_token: clientCredentials.data.refresh_token,
      expires_in: clientCredentials.data.expires_in // seconds
    };
  },
  refresh: true,
  performRefreshTokenRequest: async (credentials) => {
    const getTokenUrl = `${authUrl}/oauth/token?refresh=true&grant_type=client_credentials`;
    const headers = {'Authorization': `Bearer ${credentials.refreshToken}`};

    const clientCredentials = await axios.get(getTokenUrl, { headers });

    return {
      access_token: clientCredentials.data.access_token,
      refresh_token: clientCredentials.data.refresh_token,
      expires_in: clientCredentials.data.expires_in // seconds
    };
  }
};
```

### Fields Reference

#### Label

```yml
{
  label: 'API Credentials'
}
```

#### Input

```yml
{
  key: 'username',
  label: 'Username'
}
```

Additional properties:

- `type: 'password' | 'textarea'` - Input type.
- `helpText: string` - Help text for the field.

####  Select

```yml
{
  key: 'server',
  label: 'Data center',
  type: 'select',
  defaultValue: '1',
  options: [
    {
      value: '1',
      label: 'USA'
    },
    {
      value: '2',
      label: 'EU'
    }
  ]
}
```

#### File

```yml
{
  key: 'fileSecret',
  label: 'Upload file',
  type: 'file',
  accept: '.txt'
}
```

### `performGetTokenRequest` Function

This function is called when the user submits the login form. It should return an object with the following properties: `access_token`, `refresh_token`, and `expires_in`. The `expires_in` property should be in seconds.

### `performRefreshTokenRequest` Function

If the access token has an expiration date and needs to be refreshed, set `refresh: true` and implement the `performRefreshTokenRequest` function.

This function should return an object with the following properties: `access_token`, `refresh_token`, and `expires_in`. The `expires_in` property should be in seconds.

## OAuth2 Support

If the third-party service uses OAuth2 for authorization, use the `oauthLogin` field to configure it.

**Caution:** The `loginForm` in this case should remain undefined.

The `oauthLogin` property allows you to customize many different properties, mappings, etc. So that you can integrate with any OAuth2 implementation.

**Main default values:**

- Redirect uri prefix: `/oauth/code`.
- Client ID field name in url parameters and in request payload: `client_id`.
- Client Secret field name in request payload: `client_secret`.
- State field name in url parameters: `state`.
- Access token field name: `access_token`.
- The default assumption is that tokens do not have an expiration date, to change this behavior use the `refresh` flag to refresh the token and take expiration into account.
- Refresh token field name: `refresh_token`.
- Expires in field name: `expires_in` (value should be in seconds).

This module relies on the OAuth2 protocol being implemented by a third-party service in this way:

- Request for access token should be done via POST request to `accessTokenUrl` with JSON body that will contain at least `clientId`, `clientSecret`, `code` and `redirectUri` (also possible to add extra fields via `extraAccessTokenParameters` property).
- Request to refresh token should be done via POST request to `accessTokenUrl` (or `refreshTokenUrl` if defined) with JSON body that will contain at least `clientId`, `clientSecret` and `refreshToken` (also possible to add extra fields via `extraRefreshTokenParameters` property).
- Both requests will return JSON response with body that contains `accessToken` and, if enabled, `refreshToken` (optional) and `expireIn`.

To override these requests, please use `performGetTokenRequest` and `performRefreshTokenRequest` (e.g. if requests should be made with different HTTP methods or data should be passed as query string or form data).

### GitHub Example

```js title="index.js"
configuration.projectIntegration.oauthLogin = {
  authorizationUrl: 'https://github.com/login/oauth/authorize',
  clientId: 'github_app_client_id',
  clientSecret: 'github_app_client_secret',
  accessTokenUrl: 'https://github.com/login/oauth/access_token'
}
```

### Google Example

```js title="index.js"
configuration.projectIntegration.oauthLogin = {
  scope: 'https%3A//www.googleapis.com/auth/userinfo.email',
  authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
  clientId: 'google_web_app_client_id',
  clientSecret: 'google_web_app_client_secret',
  accessTokenUrl: 'https://oauth2.googleapis.com/token',
  extraAutorizationUrlParameters: {
    response_type: 'code',
    access_type: 'offline',
    prompt: 'consent'
  },
  extraAccessTokenParameters: {
    grant_type: 'authorization_code'
  },
  extraRefreshTokenParameters: {
    grant_type: 'refresh_token'
  },
  refresh: true
}
```

### Mailup Example

```js title="index.js"
const clientId = 'client_id';
const clientSecret = 'client_secret';
const tokenUrl = 'https://services.mailup.com/Authorization/OAuth/Token';

configuration.projectIntegration.oauthLogin = {
  authorizationUrl: 'https://services.mailup.com/Authorization/OAuth/LogOn',
  clientId,
  clientSecret,
  extraAutorizationUrlParameters: {
    response_type: 'code'
  },
  refresh: true,
  performGetTokenRequest: async ({ code, query, url, redirectUri, loginForm }) => {
    //query is an object with all query params
    //url is an url string that OAuth server used to call us back
    const getTokenUrl = `${tokenUrl}?code=${code}&grant_type=authorization_code`;
    const headers = {
      'Authorization': `Bearer ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`
    };
    return (await axios.get(getTokenUrl, { headers })).data;
  },
  performRefreshTokenRequest: async ({ credentials, loginForm }) => {
    const params = {
      refresh_token: credentials.refreshToken,
      grant_type: 'refresh_token',
      client_id: clientId,
      client_secret: clientSecret
    };
    const data = Object.keys(params)
      .map((key) => `${key}=${encodeURIComponent(params[key])}`)
      .join('&');
    const headers = {
      'Content-Type': 'application/x-www-form-urlencoded'
    };
    return (await axios.post(tokenUrl, data, { headers })).data;
  }
}
```

### Customization

In addition, you can specify extra fields on login screen that you can use to dynamically build authorization url:

```js title="index.js"
configuration.projectIntegration.oauthLogin.loginFields = [
  {
    key: 'region',
    label: 'Region',
    type: 'select',
    defaultValue: 'NA',
    options: [
      {
        value: 'NA',
        label: 'North American'
      },
      {
        value: 'EU',
        label: 'Europe'
      },
      {
        value: 'AZURE_NA',
        label: 'Azure North American'
      }
    ]
  }
];

//NOTE: url should not append state! `state` is utilized by framework
configuration.projectIntegration.oauthLogin.getAuthorizationUrl = ({ redirectUrl, loginForm }) => {
  const region = loginForm.region;
  if (region === 'EU') {
    return `https://eu-region.com?client_id=<client-id>&redirect_uri=${redirectUrl}`;
  } else {
    return `https://default-region.com?client_id=<client-id>&redirect_uri=${redirectUrl}`;
  }
};
```

By default, OAuth works in a way that app opens popup, where user performs login, and on successful action this popup will be closed and main window will receive an event and will finalize the login flow. But in some rare cases there might be an issue of sending this event to main window. In this case please use `polling` mode.

```js title="index.js"
configuration.projectIntegration.oauthLogin.mode = 'polling';
```