I have just started working with the OneDrive API and the sample program that comes with it (OneDriveApiBrowser).
As expected, the first time I logged in (using "Sign In to MSA...", I was asked for credentials, my 2-factor code, and finally a permissions screen asking if I approve of the access that the app wants against my OneDrive account.
But, after I exit the program and restart it, I am not logged in. I repeat going to "Sign In to MSA..." and I am no longer prompted for credentials (as I expected), but I am prompted with the permissions screen again.
Is there a way of having the app log back in without always prompting the user for permission?
For learning how to use the OneDrive API, I am just using the sample code that Microsoft supplied as part of the API located at https://github.com/OneDrive/onedrive-sdk-csharp/tree/master/samples/OneDriveApiBrowser. The code can't be downloaded directly from there, but at the root of this project, https://github.com/OneDrive/onedrive-sdk-csharp. This will download the source for the API as well as the sample code and unit tests.
After doing some more poking around, I finally found out how to do this. My explanation here will be in the context of the sample program mentioned in the original question above.
In the program, in the SignIn
method, there was some setup being done which included calling OneDriveClient.GetMicrosoftAccountClient(...)
, then calling the following:
if (!this.oneDriveClient.IsAuthenticated)
{
await this.oneDriveClient.AuthenticateAsync();
}
So, two things needed to be done. We need the save the result from the code above, then save the RefreshToken value somewhere safe... (It's just a very long string).
if (!this.oneDriveClient.IsAuthenticated)
{
AccountSession accountSession = await this.oneDriveClient.AuthenticateAsync();
// Save accountSession.RefreshToken somewhere safe...
}
Finally, I needed to put an if
around the OneDriveClient.GetMicrosoftAccountClient(...)
call and only call it if the saved refresh token has not been saved yet (or as been deleted due to code added to the logout call...) If we have a saved refresh token, we call `OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient(...) instead. The entire SignIn method looks like this when I am done.
private async Task SignIn(ClientType clientType)
{
string refreshToken = null;
AccountSession accountSession;
// NOT the best place to save this, but will do for an example...
refreshToken = Properties.Settings.Default.RefreshToken;
if (this.oneDriveClient == null)
{
if (string.IsNullOrEmpty(refreshToken))
{
this.oneDriveClient = clientType == ClientType.Consumer
? OneDriveClient.GetMicrosoftAccountClient(
FormBrowser.MsaClientId,
FormBrowser.MsaReturnUrl,
FormBrowser.Scopes,
webAuthenticationUi: new FormsWebAuthenticationUi())
: BusinessClientExtensions.GetActiveDirectoryClient(FormBrowser.AadClientId, FormBrowser.AadReturnUrl);
}
else
{
this.oneDriveClient = await OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient(FormBrowser.MsaClientId,
FormBrowser.MsaReturnUrl,
FormBrowser.Scopes,
refreshToken);
}
}
try
{
if (!this.oneDriveClient.IsAuthenticated)
{
accountSession = await this.oneDriveClient.AuthenticateAsync();
// NOT the best place to save this, but will do for an example...
Properties.Settings.Default.RefreshToken = accountSession.RefreshToken;
Properties.Settings.Default.Save();
}
await LoadFolderFromPath();
UpdateConnectedStateUx(true);
}
catch (OneDriveException exception)
{
// Swallow authentication cancelled exceptions
if (!exception.IsMatch(OneDriveErrorCode.AuthenticationCancelled.ToString()))
{
if (exception.IsMatch(OneDriveErrorCode.AuthenticationFailure.ToString()))
{
MessageBox.Show(
"Authentication failed",
"Authentication failed",
MessageBoxButtons.OK);
var httpProvider = this.oneDriveClient.HttpProvider as HttpProvider;
httpProvider.Dispose();
this.oneDriveClient = null;
}
else
{
PresentOneDriveException(exception);
}
}
}
}
For completeness, I updated the logout code
private async void signOutToolStripMenuItem_Click(object sender, EventArgs e)
{
if (this.oneDriveClient != null)
{
await this.oneDriveClient.SignOutAsync();
((OneDriveClient)this.oneDriveClient).Dispose();
this.oneDriveClient = null;
// NOT the best place to save this, but will do for an example...
Properties.Settings.Default.RefreshToken = null;
Properties.Settings.Default.Save();
}
UpdateConnectedStateUx(false);
}
The wl.offline_access
scope is required by applications to save the user consent information and refresh the access token without a UI prompt.
For more details on the scopes that you can use in your application see https://dev.onedrive.com/auth/msa_oauth.htm#authentication-scopes