I'm trying to implement a provider hosted app with the purpose of adding list items to sharepoint online sites from a scheduled job (console app) or windows service. So I've created a provider hosted app in Visual studio 2012, and managed to get necessary tokens and call SharePoint from the app web's start page. What I'm struggling With is using token helper from a console app, I get 400 Bad Request. I haven't found any info out there that suggests how refresh tokens and app only tokens can be combined, and especially no examples on no-web scenarioes.

This is what I've tried:

App Manifest

  • Checked Allow the app to make app-only Calls to SharePoint
  • Added Site Collection Scope With FullControl Permission

Default.aspx.cs Page_Load in MyAppWeb:

string contextTokenString = TokenHelper.GetContextTokenFromRequest(Page.Request);
SharePointContextToken contextToken = TokenHelper.ReadAndValidateContextToken(contextTokenString, Request.Url.Authority);

string refreshToken = contextToken.RefreshToken; // persisted to be used in console app
string realm = contextToken.Realm; // persisted to be used in console app
string targetPrincipalName = contextToken.TargetPrincipalName; // persisted to be used in console app

// All of the remaining code works perfectly
OAuth2AccessTokenResponse r1 = TokenHelper.GetAccessToken(refreshToken, targetPrincipalName, targetHost, realm);
OAuth2AccessTokenResponse r2 = TokenHelper.GetAppOnlyAccessToken(targetPrincipalName, targetHost, realm);

using (ClientContext clientContext = TokenHelper.GetClientContextWithAccessToken(sharepointUrl, r1.AccessToken))
    clientContext.Load(clientContext.Web, web => web.Title);
    Response.Write("<p>1. ------>" + clientContext.Web.Title + "</p>");

using (ClientContext clientContext = TokenHelper.GetClientContextWithAccessToken(sharepointUrl, r2.AccessToken))
    clientContext.Load(clientContext.Web, web => web.Title);
    Response.Write("<p>2. ------>" + clientContext.Web.Title + "</p>");

Program.cs in MyAppService:

string sharepointUrl = "https://mytenant.sharepoint.com";
string refreshToken = "the refresh token persisted by Default.aspx";
string realm = "the realm persisted by Default.aspx";
string targetPrincipalName = "the Office 365 guid persisted by Default.aspx";
string targetHost = new Uri(sharepointUrl).Authority;

// Both of the following two lines fail with 400: bad request
string accessToken = TokenHelper.GetAccessToken(refreshToken, targetPrincipalName, targetHost, realm).AccessToken;
string accessToken = TokenHelper.GetAppOnlyAccessToken(targetPrincipalName, targetHost, realm).AccessToken;

I was able to use the following code from Program.cs with an access token obtained from Default.aspx, but that expired after 12 hours.

string accessToken = "Access token persisted by Default.aspx";
using (ClientContext clientContext = TokenHelper.GetClientContextWithAccessToken(sharepointUrl, accessToken))
            clientContext.Load(clientContext.Web, web => web.Title);
            string title = clientContext.Web.Title;
            Console.WriteLine("------> " + title);

I must also confess that I have sligthly modified the TokenHelper class I copied from the AppWeb project to the console project to support reading ClientId and ClientSecret from App.config instead of Web.Config. Basically I changed the code to use ConfigurationManager instead of WebConfigurationManager.

3 Answers 3


In your case you should to use Refresh token and get Access token see example below:

Uri host         = new Uri("*SP Site");
var refreshToken = "*Refresh Token";
var provider     = TokenHelper.GetAccessToken(refreshToken, TokenHelper.SharePointPrincipal, host.Authority, TokenHelper.GetRealmFromTargetUrl(host));

using (var context = TokenHelper.GetClientContextWithAccessToken(host.ToString(), provider.AccessToken))

I see your code and probably you use wrong targetPrincipalName and realm variables for GetAccessToken method.

P.S.: Also you should remember, Refresh Token valid only for 6 months.


After having problems with this myself for quite some Time I found out where that HTTP 400 Bad Request comes from when calling TokenHelper.GetAppOnlyAccessToken()

You don't show what the variable targetHost is in your Code Snippet:

OAuth2AccessTokenResponse r2 = TokenHelper.GetAppOnlyAccessToken(targetPrincipalName, targetHost, realm);

I expect you used the SPHostUrl Request Parameter for that. But the GetAppOnlyAccessToken Method requires an

Url authority of the target principal

So you have to pass just the Authority of the Host Web here, in order to get a correct OAuth2AccessTokenRequest

Here is a working example:

// Get the SharePoint Context Token
SharePointContextToken contextToken = TokenHelper.ReadAndValidateContextToken(Request["SPHostUrl"], Request.Url.Authority);

// Parameters required to obtain the App Only Context Token
string targetPrincipalName = contextToken.TargetPrincipalName;
Uri hostWeb = new Uri(Request["SPHostUrl"]); // Note that this is an URI not a plain URL String
string realm = contextToken.Realm;

// Obtain the App-Only Access Token String
string appOnlyAccessToken = TokenHelper.GetAppOnlyContextToken(targetPrincipalName
, hostWeb.Authority /*Pass the Authority of the Host Web Uri*/
, realm).AccessToken;

// Create your Client Context (finally)
using (ClientContext cC = TokenHelper.GetClientContextWithAccessToken(hostWeb.toString(), appOnlyAccessToken)
    // Do your stuff here

6 years late to party, (kind of starting my career that time). I too was facing the issue, in my case client id and client secret entries were missing from console application's app.config.

using below code:

var appCatalogURI = new Uri(tenantAppCatalogURL);
string appCatRealm = TokenHelper.GetRealmFromTargetUrl(appCatalogURI);
var appCatToken = TokenHelper.GetAppOnlyAccessToken(
                  appCatalogURI.Authority, appCatRealm).AccessToken;// getting 400 bad request here

after adding

<add key="ClientId" value=""/>
<add key="ClientSecret" value=""/>

in app.config error went away.

Got the idea from: https://tejinderrai.org/tag/sharepoint-apps/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.