OAuth Authorization in /n software Cloud Products
Requirements: Cloud Storage, Cloud Mail, Cloud Calendars, Cloud Sheets, or Cloud Keys
Many of the services supported by the /n software cloud products use OAuth 2.0 for authorization. The components for these services share the same built-in OAuth support, which consists of an OAuth property and an Authorize method that handle the entire OAuth flow, including obtaining, caching, and refreshing access tokens.
This article covers how to use this built-in support in desktop applications, web applications, and unattended (server-to-server) applications. Since the OAuth support is shared, the examples below apply to any of the components listed above, even though each example uses a specific component.
For provider-specific notes and behavior, such as default URLs, scopes, and additional required settings, please refer to the documentation for the corresponding component.
Contents
- How the Built-In OAuth Support Works
- Desktop Applications
- Persisting Refresh Tokens
- Web Applications
- Unattended Applications
- Using an Existing Access Token
How the Built-In OAuth Support Works
Before an OAuth-based component can make requests, it must be authorized. The OAuth property holds the settings for the OAuth flow, such as the client ID and secret of the application registered with the service provider. Calling the Authorize method performs the flow and obtains an access token, which the component then uses in all of its requests.
The flow is selected with two fields of the OAuth property:
- ClientProfile specifies the type of application. cocpApplication (the default) is for applications run directly by the user, such as desktop applications. cocpWeb is for server-side applications that a user accesses through a web browser.
- GrantType specifies the OAuth grant type. cogtAuthorizationCode (the default) signs in a user interactively. cogtClientCredentials authorizes the application itself, without a user. The cogtImplicit and cogtPassword grant types are also available, but are considered insecure and should only be used when necessary.
Each component has default values for the service provider's authorization and token URLs, so in many cases only the client ID and secret need to be set.
The components cache the access token along with its expiration time. When a method that makes a request is called, the component checks whether the access token has expired, and if so, automatically obtains a new one when it can do so without user interaction.
Some components, such as CloudFiles in Cloud Storage and CloudMailer in Cloud Mail, work with several service providers. For these components, the OAuth property applies to the service provider selected by the ServiceProvider property, so ServiceProvider must be set before configuring the OAuth property; otherwise, the values set in the OAuth property are not used.
Desktop Applications
Desktop applications use the cocpApplication client profile with the cogtAuthorizationCode grant type, which are the defaults. When the Authorize method is called, the component starts an embedded web server and launches a browser so the user can sign in and grant access to the application. Once access is granted, the service redirects the browser back to the embedded web server, and the component automatically exchanges the authorization code it receives for an access token. Proof Key for Code Exchange (PKCE) is used by default to protect the exchange, as controlled by the OAuthUsePKCE configuration setting.
The address of the embedded web server must be registered as a redirect URI for the application in the service provider's portal. By default, the embedded web server listens on a random port, but most service providers require the redirect URI to match exactly. To use a fixed port, set the OAuthWebServerPort configuration setting, and register http://localhost:PORT as the redirect URI.
GoogleDrive googledrive = new GoogleDrive();
googledrive.OAuth.ClientProfile = CloudOAuthClientProfiles.cocpApplication;
googledrive.OAuth.GrantType = OAuthSettingsGrantTypes.cogtAuthorizationCode;
googledrive.OAuth.ClientId = "CLIENT_ID";
googledrive.OAuth.ClientSecret = "CLIENT_SECRET";
// http://localhost:7777 must be registered as a redirect URI for the application.
googledrive.Config("OAuthWebServerPort=7777");
googledrive.Authorize();
// The component is now authorized.
googledrive.ListResources();
The Timeout field of the OAuth property specifies how long (in seconds) the component waits for the user to complete the sign-in in the browser. The default value is 60. The HTML page shown to the user after signing in can be customized with the OAuthWebServerResponse configuration setting.
Persisting Refresh Tokens
Access tokens are only valid for a limited time (the lifetime, in seconds, is available through the OAuthAccessTokenExpiration configuration setting). To avoid requiring the user to sign in again each time the application runs, the component requests a refresh token, which can be used to obtain new access tokens without user interaction.
The RequestRefreshToken field of the OAuth property is enabled by default. When enabled, the component automatically adds whatever the service provider requires to issue a refresh token:
- For Google services, the access_type=offline parameter is added.
- For Dropbox, the token_access_type=offline parameter is added.
- For Microsoft services, the offline_access scope is added.
- Box and ShareFile issue refresh tokens without any additional parameters.
After Authorize returns, the refresh token is available in the RefreshToken field of the OAuth property. To reuse it later, store it securely, and assign it to the RefreshToken field before calling Authorize the next time the application runs. When a refresh token is set, Authorize uses it to obtain a new access token without launching the browser.
Dropbox dropbox = new Dropbox();
dropbox.OAuth.ClientId = "CLIENT_ID";
dropbox.OAuth.ClientSecret = "CLIENT_SECRET";
dropbox.Config("OAuthWebServerPort=7777");
// Restore the refresh token saved by a previous run, if one exists.
string savedToken = LoadRefreshToken();
if (!string.IsNullOrEmpty(savedToken)) dropbox.OAuth.RefreshToken = savedToken;
// The browser is only launched if no valid refresh token is available.
dropbox.Authorize();
// Save the refresh token for the next run.
SaveRefreshToken(dropbox.OAuth.RefreshToken);
In the example above, LoadRefreshToken and SaveRefreshToken are placeholders for application code that reads and writes the token from secure storage. Some service providers issue a new refresh token each time one is used, so the value of the RefreshToken field should be saved again after each call to Authorize.
While the application runs, the component refreshes expired access tokens automatically. If an access token cannot be obtained without user interaction and Authorize was not called, the component throws an error instead of launching the browser. To only refresh the access token when Authorize is called, disable the OAuthAutomaticRefresh configuration setting.
Web Applications
In web applications, the user's browser runs on a different machine than the component, so the component cannot launch a browser or use its embedded web server. Instead, the cocpWeb client profile is used, and the application redirects the user to the service provider and receives the authorization code itself. This flow typically spans two separate requests to the web application:
- Set the ReturnURL field to the application's callback URL (which must be registered as a redirect URI with the service provider), and query the WebAuthURL field to get the authorization URL. Then, redirect the user's browser to that URL.
- After the user grants access, the service provider redirects the browser to the callback URL with the authorization code in the code query parameter. Set the AuthorizationCode field to this value, and call Authorize to exchange it for an access token.
Since PKCE is enabled by default, the PKCE verifier used to build the authorization URL must also be used when exchanging the code. When the two steps use different instances of the component, read the verifier from the OAuthPKCEVerifier configuration setting in the first step, store it with the user's session, and set it again in the second step.
// Request 1: Redirect the user to the service provider.
Box box = new Box();
box.OAuth.ClientProfile = CloudOAuthClientProfiles.cocpWeb;
box.OAuth.ClientId = "CLIENT_ID";
box.OAuth.ClientSecret = "CLIENT_SECRET";
box.OAuth.ReturnURL = "https://app.example.com/oauth/callback";
string authUrl = box.OAuth.WebAuthURL;
// Store the PKCE verifier with the user's session, then redirect the browser to authUrl.
session["pkceVerifier"] = box.Config("OAuthPKCEVerifier");
// Request 2: Handle the callback and exchange the authorization code.
Box box = new Box();
box.OAuth.ClientProfile = CloudOAuthClientProfiles.cocpWeb;
box.OAuth.ClientId = "CLIENT_ID";
box.OAuth.ClientSecret = "CLIENT_SECRET";
box.OAuth.ReturnURL = "https://app.example.com/oauth/callback";
box.Config("OAuthPKCEVerifier=" + session["pkceVerifier"]);
box.OAuth.AuthorizationCode = request.QueryString["code"];
box.Authorize();
// Store the refresh token for this user so that later requests can call Authorize without redirecting again.
SaveRefreshToken(userId, box.OAuth.RefreshToken);
In the example above, session, request, and SaveRefreshToken are placeholders for the web framework's session state, the incoming request, and the application's storage. To protect against cross-site request forgery, a state parameter can be added to the authorization URL with the OAuthParamCount, OAuthParamName[i], and OAuthParamValue[i] configuration settings. The service provider returns the same value in the callback, where the application should verify it.
Unattended Applications
Services, scheduled tasks, and other applications that run without a user present can use the cogtClientCredentials grant type, where the application authorizes as itself instead of as a user. No browser or redirect URI is involved, and the component automatically obtains a new access token when the current one expires. This grant type is supported by Microsoft's identity platform, and requires additional setup in the Azure Portal. The application must be granted application permissions, and an administrator must grant consent for them.
When using client credentials with Microsoft services, the token URL must contain the tenant ID instead of common, and the scope must be the .default scope of the resource. For services accessed through Microsoft Graph, such as OneDrive, SharePoint, and Office 365, the scope is https://graph.microsoft.com/.default.
Since there is no signed-in user, these components must also be told which user's data to access. In Cloud Storage, the OneDrive and SharePoint components use the UserId configuration setting for this purpose.
OneDrive onedrive = new OneDrive();
onedrive.OAuth.GrantType = OAuthSettingsGrantTypes.cogtClientCredentials;
onedrive.OAuth.ClientId = "CLIENT_ID";
onedrive.OAuth.ClientSecret = "CLIENT_SECRET";
onedrive.OAuth.ServerTokenURL = "https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token";
onedrive.OAuth.AuthorizationScope = "https://graph.microsoft.com/.default";
// The ID of the user whose OneDrive will be accessed.
onedrive.Config("UserId=USER_ID");
onedrive.Authorize();
In Cloud Mail, the Office365 component uses the UserId property instead, which can be set to either the ID or the user principal name (typically the email address) of the user whose mailbox will be accessed.
Office365 office365 = new Office365();
office365.OAuth.GrantType = OAuthSettingsGrantTypes.cogtClientCredentials;
office365.OAuth.ClientId = "CLIENT_ID";
office365.OAuth.ClientSecret = "CLIENT_SECRET";
office365.OAuth.ServerTokenURL = "https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token";
office365.OAuth.AuthorizationScope = "https://graph.microsoft.com/.default";
// The mailbox that will be accessed.
office365.UserId = "user@example.com";
office365.Authorize();
For other Microsoft services, the scope is the .default scope of that service's resource. For example, for Azure Blob Storage, the scope is the .default scope of the storage account.
AzureBlob blob = new AzureBlob();
blob.Account = "ACCOUNT";
blob.OAuth.GrantType = OAuthSettingsGrantTypes.cogtClientCredentials;
blob.OAuth.ClientId = "CLIENT_ID";
blob.OAuth.ClientSecret = "CLIENT_SECRET";
blob.OAuth.ServerTokenURL = "https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token";
blob.OAuth.AuthorizationScope = "https://ACCOUNT.blob.core.windows.net/.default";
blob.Authorize();
Using an Existing Access Token
For more control over the flow, or when an access token is obtained by other means (such as the separate OAuth component or an existing identity system), the token can be assigned directly to the Authorization property. The value must include the token type, for example Bearer ACCESS_TOKEN. When the Authorization property is set, the component ignores the values in the OAuth property and does not refresh the token automatically.
dropbox.Authorization = "Bearer " + accessToken;
dropbox.ListResources("");
In the Cloud Mail CloudMailer component, the token is instead assigned to the Authorization field of the Account property.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.