Skip to content
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

Research access and refresh tokens behaviours #109918

Closed
mikecote opened this issue Aug 24, 2021 · 4 comments
Closed

Research access and refresh tokens behaviours #109918

mikecote opened this issue Aug 24, 2021 · 4 comments
Assignees
Labels
estimate:needs-research Estimated as too large and requires research to break down into workable issues Feature:Actions/ConnectorTypes Issues related to specific Connector Types on the Actions Framework Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams)

Comments

@mikecote
Copy link
Contributor

We should leverage the authorization code flow POC (#107446) to research and understand what happens to access tokens after we refresh them. Do previous access tokens still work?

With the same POC, we should research how we plan to detect when an access token is expired. This logic will help us understand when to refresh an access token. Is there a way to know how soon it will expire? Are there rate limits to how frequently we can refresh a token?

We should also research if Microsoft Exchange supports refresh token rotation. If so, how many times can a refresh token be rotated?

@mikecote mikecote added loe:needs-research This issue requires some research before it can be worked on or estimated Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) Feature:Actions/ConnectorTypes Issues related to specific Connector Types on the Actions Framework labels Aug 24, 2021
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-alerting-services (Team:Alerting Services)

@chrisronline
Copy link
Contributor

chrisronline commented Aug 31, 2021

Findings

OAuth Authorization Code Flow

For this flow, we need the user to configure the Oauth provider to provide user consent in order to obtain a valid access token. This access token is short-lived and usually expires in about a day. In order to obtain a new access token, the user either needs to go through the above flow again, or we need to request an additional permission from the user: offline_access. This permission will ensure that we obtain a refresh token when obtaining a valid access token. This refresh token can be used to obtain a new access token without requiring user consent or action.

The refresh token is longer lived, defaults to 90 days, but can expire due to inactivity (depends on how the user has configured their Microsoft service - see previous link). If the refresh token expires, the user will need to go through the initial flow again to obtain a valid access token.

(It's worth noting that it seems the ability to configure the duration of the refresh token has recently been removed)

Client credentials flow (Graph API)

For this flow, we need the user to configure the Oauth provider to provide admin consent in order to obtain a valid access token. This access token is short-lived and usually expires in about a day. In order to obtain a new access token, we just need to call the same Microsoft API with the already provided oauth provider details.

We can keep requesting new access tokens indefinitely until the admin consent is revoked. At this point, the user will need to re-consent for us to obtain a new, valid access token. Consent simply involves the user visiting a url like https://login.microsoftonline.com/{tenant}/adminconsent?client_id={client_id}&state={state}&redirect_uri={redirect_uri}

If we attempt to obtain an access token and consent is not provided, we see an error like:

{
  "error": {
    "code": "Authorization_RequestDenied",
    "message": "Insufficient privileges to complete the operation.",
    "innerError": {
      "date": "2021-08-31T15:54:33",
      "request-id": "c7f4539a-c7eb-4dfc-80c7-7a27aab41c89",
      "client-request-id": "c7f4539a-c7eb-4dfc-80c7-7a27aab41c89"
    }
  }
}

If consent was provided and we obtained a valid access token, but later that consent was revoked, the existing access token will continue to work, but when we request a new one, we will see the same error as above.

Questions

Do previous access tokens still work?

Yes, in both flows, the access token is still valid until the expires_in is reached, regardless if additional access tokens are requested.

We should research how we plan to detect when an access token is expired.

Once an access token expires, Microsoft will return a specific kind of error that we can detect and then request a new access token. Unfortunately, the PoC isn't fleshed out far enough to see where our logic might live to refresh the access token, but I imagine it'd simply live in the email connector

Is there a way to know how soon it will expire

Yup! Like mentioned above, we receive an expires_in data point from requesting an access token that will tell us when it will expire. I don't know if we will need to necessarily use this, but it is an option.

Are there rate limits to how frequently we can refresh a token?

I did not find anything in the documentation, nor did I encounter an issue while testing.

My testing code
async function getNewTokens(refreshToken) {
  const data = {
    redirect_uri: 'https://localhost:5601/api/actions/connector/oauth_code',
    scope: 'https://outlook.office.com/SMTP.Send offline_access',
    client_id: '',
    client_secret: ',
    grant_type: 'refresh_token',
    refresh_token: refreshToken,
  }
  const params = new URLSearchParams(data);
  const { data: result } = await axios({
    method: 'POST',
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
    data: params.toString(),
    url: 'https://login.microsoftonline.com/dfd49923-fdf1-4fd2-a058-3ec1f69b4f41/oauth2/v2.0/token',
  });
  accessTokens.push({
    access_token: result.access_token,
    expires_in: result.expires_in,
    created_using_refresh_token: refreshToken,
    refresh_token: result.refresh_token,
  });
  refreshTokens.push(result.refresh_token);
  return result.refresh_token;
}

async function generateTokens(count) {
  let refreshToken = startingRefreshToken;
  for (let i = 0; i < count; i++) {
    console.log(`Creating token ${i}...`)
    try {
      refreshToken = await getNewTokens(refreshToken)
    } catch (err) {
      console.error(`Unable to create token ${i} due to ${err.message} using '${refreshToken}'`)
    }
  }
  console.log(`Generated ${accessTokens.length} tokens...`)
}

async function runIterations(count) {
  for (let i = 0; i < count; i++) {
    const index = Math.floor(
      Math.random() * accessTokens.length
    );
    const ran = accessTokens[index];
    console.log(`Iteration ${i} using ${index}`);
    try {
      await send(ran.access_token);
    } catch (err) {
      console.error(`Unable to send mail due to ${err.message}`, { ran });
    }
  }
}

await generateTokens(100);
await runIterations(100);
await generateTokens(2);
await runIterations(15);

if Microsoft Exchange supports refresh token rotation

It does, as every time you request a new access token, it will return a new refresh token. It's worth noting that previously created refresh tokens still work as well (they aren't invalidated when a new refresh token is generated). You can control control refresh token duration through the Microsoft admin portal

how many times can a refresh token be rotated?

Like in my testing code above, I didn't encounter any limit when using refresh tokens. I was able to use the original refresh token over and over, as well as using any of the previously returned refresh tokens.

@gmmorris gmmorris added estimate:needs-research Estimated as too large and requires research to break down into workable issues and removed loe:needs-research This issue requires some research before it can be worked on or estimated labels Sep 2, 2021
@gmmorris
Copy link
Contributor

gmmorris commented Sep 7, 2021

Can this be closed now that the research has concluded? 🤔

@chrisronline
Copy link
Contributor

Yes!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
estimate:needs-research Estimated as too large and requires research to break down into workable issues Feature:Actions/ConnectorTypes Issues related to specific Connector Types on the Actions Framework Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams)
Projects
None yet
Development

No branches or pull requests

5 participants