XeroAPI / Xero-Postman-Tutorial-PKCE-Edition

3 stars 6 forks source link

Refresh access token #3

Open ManvirDynamic opened 4 years ago

ManvirDynamic commented 4 years ago

We have build a c# console app using PKCE and it seems to be working fine. Only problem is that we are not able to refresh access token, in OAuth2 we were doing as below: XeroClient xeroClient = GetXeroClientInstance(xconfig); var refreshToken = await xeroClient.RefreshAccessTokenAsync(tokenFromStorage);

However in above class "XeroClient", we have to provide ClientSecret and PKCE does not have a client secret. So how we can refresh access token in PKCE?

rippo commented 4 years ago

When you first connect with PCKE you also get a refresh token. What you need to do is to store this refresh token somewhere safe then you use this next time you connect you pass in this refresh token. Couple of things:-

  1. Every time you start your console app you will need to get a new refresh token (code below) and store the new token
  2. If you dont connect within 30 days you need to reconnect from scratch
  3. A refresh token can only be used once from what I see
public async Task<TokenDto> RefreshToken(string lastToken)
{
    var request = new RestRequest(Method.POST);
    request.AddHeader("Cache-Control", "no-cache");
    request.AddParameter("grant_type", "refresh_token", ParameterType.GetOrPost);
    request.AddParameter("refresh_token", lastToken, ParameterType.GetOrPost);
    request.AddParameter("client_id", XeroConfig.ClientId, ParameterType.GetOrPost);

    var result = await xeroTokenClient.ExecuteAsync(request);
    if (result.StatusCode == HttpStatusCode.OK)
    {
        Log.Verbose("Got new token from XERO, all OK");
        return JsonConvert.DeserializeObject<TokenDto>(result.Content);
    }

    Log.Verbose($"Error getting token. Result is {result.Content}");
    return null;
}

...
        internal async Task Refresh()
        {
            var lastToken = await dbQuery.GetLastXeroRefreshToken();
            var tokenDto = await xeroApi.RefreshToken(lastToken);

            if (tokenDto != null)
            {
                dbCommand.UpdateLastXeroRefreshToken(tokenDto);
                XeroConfig.AccessToken = tokenDto.AccessToken;
            }
        }
...

public class TokenDto
{
    [JsonProperty("id_token")]
    public string TokenId { get; set; }

    [JsonProperty("access_token")]
    public string AccessToken { get; set; }

    [JsonProperty("expires_in")]
    public int ExpiresIn { get; set; }

    [JsonProperty("token_type")]
    public string TokenType { get; set; }

    [JsonProperty("refresh_token")]
    public string RefreshToken { get; set; }

    [JsonProperty("scope")]
    public string Scope { get; set; }
}
ManvirDynamic commented 4 years ago

Sorry for the late reply.

We are using OidcClientOptions for credentials and login in PKCE and AccountingApi to access your API.

In your code above, how do we get xeroTokenClient as in line below, it is XeroClient as i mentioned previously? If yes than it requires XeroConfiguration and XeroConfiguration requires ClientSecret. var result = await xeroTokenClient.ExecuteAsync(request);

Our code is as follows:

It is working fine till last where I get organisations, can you please tell how to refresh after that.

var options = new OidcClientOptions { Authority = "https://identity.xero.com",
ClientId = "",
Scope = "openid profile email offline_access files accounting.transactions accounting.contacts accounting.settings",
RedirectUri = "http://localhost:5000/signin-oidc", FilterClaims = false, Flow = OidcClientOptions.AuthenticationFlow.AuthorizationCode, ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect, LoadProfile = true }; options.Policy.Discovery.ValidateEndpoints = false; var client = new OidcClient(options); Process.Start(state.StartUrl);
var context = await http.GetContextAsync(); result = await client.ProcessResponseAsync(context.Request.Url.AbsoluteUri, state); var accountingApi = new AccountingApi(); IXeroToken token = new XeroOAuth2Token(); token.AccessToken = result.AccessToken; token.RefreshToken = result.RefreshToken; token.ExpiresAtUtc = result.AccessTokenExpiration; token.IdToken = result.IdentityToken;

var organisations = await accountingApi.GetOrganisationsAsync(token.AccessToken, XeroHelper.GetTenantId());

How to refresh token(i.e IXeroToken token) now???

In OAUTH2 we do as below, but that involves XeroConfiguration XeroClient xeroClient = GetXeroClientInstance(xconfig); var refreshToken = await xeroClient.RefreshAccessTokenAsync(tokenFromStorage); //use the latest token you have

wobinb commented 4 years ago

I would suggest raising this over on the issue page for our .net SDK. The team that maintain the SDK monitor that repo. https://github.com/XeroAPI/Xero-NetStandard/issues