I'm looking for the examples to consume the Sharepoint REST API from a C# Console application (read a Sharepoint list to be more exact). There are some tutorials from MS website but they are incomplete in my opinion. For example, this one doesn't show how to acquire the access token and I cannot find any demo code for that:
https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/complete-basic-operations-using-sharepoint-rest-endpoints
This tutorial is exactly what I need, but the code is not working: https://blog.vgrem.com/2015/04/04/consume-sharepoint-online-rest-service-using-net/
private static CookieContainer GetAuthCookies(Uri webUri, string userName, string password)
{
var securePassword = new SecureString();
foreach (var c in password) { securePassword.AppendChar(c); }
var credentials = new SharePointOnlineCredentials(userName, securePassword);
var authCookie = credentials.GetAuthenticationCookie(webUri);
var cookieContainer = new CookieContainer();
cookieContainer.SetCookies(webUri, authCookie);
return cookieContainer;
}
What doesn't work is this line var authCookie = credentials.GetAuthenticationCookie(webUri);. It returns null all the time even though all the webUri, userName, password are correct.
Can someone point me to the right direction or give me an example of client code? The server is running Sharepoint 2013.
My test code for your reference:
static void Main(string[] args)
{
HttpWebRequest endpointRequest = (HttpWebRequest)HttpWebRequest.Create("http://sp/_api/web");
endpointRequest.Method = "GET";
endpointRequest.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
endpointRequest.Credentials = System.Net.CredentialCache.DefaultCredentials;
//HttpWebResponse endpointResponse = (HttpWebResponse)endpointRequest.GetResponse();
try
{
WebResponse webResponse = endpointRequest.GetResponse();
Stream webStream = webResponse.GetResponseStream();
StreamReader responseReader = new StreamReader(webStream);
string response = responseReader.ReadToEnd();//results
responseReader.Close();
Console.WriteLine(response);
Console.ReadLine();
}
catch (Exception e)
{
Console.Out.WriteLine(e.Message); Console.ReadLine();
}
}
Or you could use this Credentials:
var username = "administrator";
var password = "P#ssw0rd";
var domain = "contoso";
endpointRequest.Credentials=new System.Net.NetworkCredential(username, password, domain);
SharePoint 2013 does not need to generate the access token.
Related
I was authenticating SharePoint Online users via an API interface. For last couple of years it has been working fine. But Since Monday i am getting error
Value cannot be null. Parameter name: cookieHeader
There is a code to generate HTTPClientHandler for SharePoint Online in API that is hosted on Azure.
HttpClientHandler result = new HttpClientHandler();
try
{
SecureString securePassword = new SecureString();
foreach (char c in userPassword) { securePassword.AppendChar(c); }
SharePointOnlineCredentials credentials = new SharePointOnlineCredentials(userName, securePassword);
result.Credentials = credentials;
string authCookieValue = credentials.GetAuthenticationCookie(new Uri(hostWebURL));
result.CookieContainer.SetCookies(new Uri(hostWebURL), authCookieValue);
}
catch (Exception ex)
{
throw ex;
}
return result;
In the above line of code, we are getting null value of ‘authCookieValue’.
I also checked the value of ‘LegacyAuthProtocolsEnabled’ through SharePoint Online Management Shell using below command, it was already true.
$TenantSettings = Get-SPOTenant
$TenantSettings.LegacyAuthProtocolsEnabled
I started getting the same error at some point. It was clearly a change on the Sharepoint Online side, because nothing had changed on my side for years.
I think the key line that fixed it for me was:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
See below:
public async Task<List<SharepointDocumentDetail>> GetFileInfoFromSharepoint(string specificFolderUrl)
{
// Once I added this, the error went away
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
List<SharepointDocumentDetail> ret = new List<SharepointDocumentDetail>();
var restUrl = string.Format("{0}/_api/web/GetFolderByServerRelativeUrl('{1}')/Files?$expand=Files", this.Sharepoint_DocRootUrl, specificFolderUrl);
//Creating Credentials
var passWord = new SecureString();
foreach (var c in this.Sharepoint_Password) passWord.AppendChar(c);
var credential = new SharePointOnlineCredentials(this.Sharepoint_User, passWord);
using (var handler = new HttpClientHandler() { Credentials = credential })
{
Uri uri = new Uri(this.Sharepoint_DocRootUrl);
// I was getting the error on this line
handler.CookieContainer.SetCookies(uri, credential.GetAuthenticationCookie(uri));
...
...
...
I have the following SharePoint CSOM code inside my c# console application to send an email using the office 365 admin username and password :-
static private void sendemail(ClientContext context, string subject, string body, FieldUserValue[] to, string username, SecureString passWord)
{
try
{
using (MailMessage mail = new MailMessage())
{
mail.From = new MailAddress("sharepoint#***.com");
mail.Subject = subject;
mail.IsBodyHtml = true;
SmtpClient client = new SmtpClient("***-com.mail.protection.outlook.com", 25);
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential(username, passWord);
client.EnableSsl = true;
mail.Body = body;
string approvalemailTo = "";
foreach (var t in to)
{
mail.To.Add(t.Email);
approvalemailTo = approvalemailTo + t.Email + ";";
}
client.Send(mail);
}
}
catch (Exception e)
{
}
}
but to make my code more secure, how i can authenticate the SmtpClient using AppID and APPSecret instead of passing the username and password?
Thanks
Your code is just standard SMTP code from the System.net.mail assembly it has no dependencies on the Sharepoint CSOM. If you want use Modern Auth in SMTP you can't do that using the client credentials flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow as per
This feature announcement is for interactive applications to enable OAuth for IMAP and SMTP. At this time, there are no plans to enable IMAP and SMTP OAuth for non-interactive applications using client credentials flow. For that, we suggest to use our Graph API.
https://techcommunity.microsoft.com/t5/exchange-team-blog/announcing-oauth-2-0-support-for-imap-and-smtp-auth-protocols-in/ba-p/1330432
You need to use Microsoft Graph API if you want to do that which would be pretty simple for you to migrate to. If you want to stick with SMTP and use Modern Auth you will need to look at using something like MailKit https://github.com/jstedfast/MailKit which supports it eg a simple sample using MSAL and Interactive Auth
String ClientId = "20773535-6b8f-4f3d-8f0e-4b7710d79afe";
string UserName = "user#domain.com";
string scope = "https://outlook.office.com/SMTP.Send";
string redirectUri = "msal20773535-6b8f-4f3d-8f0e-4b7710d79afe://auth";
string From = "Fromouser#domain.com;
String To = "Touser#domain.com";
String SMTPServer = "smtp.office365.com";
Int32 SMTPPort = 587;
PublicClientApplicationBuilder pcaConfig = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs);
pcaConfig.WithRedirectUri(redirectUri);
var TokenResult = await pcaConfig.Build().AcquireTokenInteractive(new[] { scope })
.WithPrompt(Prompt.Never)
.WithLoginHint(UserName).ExecuteAsync();
var message = new MimeMessage();
message.From.Add(MailboxAddress.Parse(From));
message.To.Add(MailboxAddress.Parse(To));
message.Subject = "Test";
message.Body = new TextPart("plain")
{
Text = #"Hey Joe"
};
using (var client = new SmtpClient())
{
client.Connect(SMTPServer, SMTPPort, SecureSocketOptions.StartTls);
var oauth2 = new SaslMechanismOAuth2(UserName, TokenResult.AccessToken);
client.Authenticate(oauth2);
await client.SendAsync(message);
client.Disconnect(true);
}
I have started to create a software architecture where i have:
Auth_API - as an auth server
Resource_API - as a resource API (protected with Auth_API)
WebApplication (mvc) - a frontend application (protected with Auth_API).
Based on https://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/ article I have successfully made a Google authentication.
WebApp == redirects to ==> Auth_API == challenge ==> Google ==> API receives externalAccessToken, registers user locally and returns localAccessToken
Now everything is OK when I would want to use bearer authorization (using local access token).
But I also want to sign in to by ASP MVC application with (cookie?) ClaimsIdentity.
I was thinking about switching to JWT, but I am not sure which way I should go...
Bit of code:
Auth_API - obtain local access token
/// <summary>
/// Returns local access token for already registered users
/// </summary>
/// <param name="provider"></param>
/// <param name="externalAccessToken"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpGet]
[Route("ObtainLocalAccessToken")]
public async Task<IHttpActionResult> ObtainLocalAccessToken(string provider, string externalAccessToken)
{
if (string.IsNullOrWhiteSpace(provider) || string.IsNullOrWhiteSpace(externalAccessToken))
{
return BadRequest("Provider or external access token is not sent");
}
var verifiedAccessToken = await VerifyExternalAccessToken(provider, externalAccessToken);
if (verifiedAccessToken == null)
{
return BadRequest("Invalid Provider or External Access Token");
}
IdentityUser user = await _repo.FindAsync(new UserLoginInfo(provider, verifiedAccessToken.user_id));
bool hasRegistered = user != null;
if (!hasRegistered)
{
return BadRequest("External user is not registered");
}
//generate access token response
var accessTokenResponse = GenerateLocalAccessTokenResponse(user.UserName);
return Ok(accessTokenResponse);
}
Generate Local Access Token algorythm
private JObject GenerateLocalAccessTokenResponse(string userName)
{
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("userName", userName),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
return tokenResponse;
}
Web application - part of login action:
if (hasLocalAccount)
{
var client = new RestClient(baseApiUrl);
var externalLoginUrl = "Account/ObtainLocalAccessToken";
var externalLoginRequest = new RestRequest(externalLoginUrl, Method.GET);
externalLoginRequest.AddQueryParameter("provider", provider);
externalLoginRequest.AddQueryParameter("externalAccessToken", externalAccessToken);
var externalLoginResponse = client.Execute(externalLoginRequest);
if (externalLoginResponse.IsSuccessful)
{
JObject response = JObject.Parse(externalLoginResponse.Content);
string localAccessToken = response["access_token"].Value<string>();
string localTokenExpiresIn = response["expires_in"].Value<string>();
// WHAT TO DO WHERE TO SIGN IN A USER ???
//AuthenticationTicket ticket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(localAccessToken); <== this returns NULL
return RedirectToAction("Index", "Home");
}
}
I want to post some request values alongside the multipart-formdata file contents. In the old API you could use PostFileWithRequest:
[Test]
public void Can_POST_upload_file_using_ServiceClient_with_request()
{
IServiceClient client = new JsonServiceClient(ListeningOn);
var uploadFile = new FileInfo("~/TestExistingDir/upload.html".MapProjectPath());
var request = new FileUpload{CustomerId = 123, CustomerName = "Foo"};
var response = client.PostFileWithRequest<FileUploadResponse>(ListeningOn + "/fileuploads", uploadFile, request);
var expectedContents = new StreamReader(uploadFile.OpenRead()).ReadToEnd();
Assert.That(response.FileName, Is.EqualTo(uploadFile.Name));
Assert.That(response.ContentLength, Is.EqualTo(uploadFile.Length));
Assert.That(response.Contents, Is.EqualTo(expectedContents));
Assert.That(response.CustomerName, Is.EqualTo("Foo"));
Assert.That(response.CustomerId, Is.EqualTo(123));
}
I can't find any such method in the new API, nor any overrides on client.Post() which suggest that this is still possible. Does anyone know if this is a feature that was dropped?
Update
As #Mythz points out, the feature wasn't dropped. I had made the mistake of not casting the client:
private IRestClient CreateRestClient()
{
return new JsonServiceClient(WebServiceHostUrl);
}
[Test]
public void Can_WebRequest_POST_upload_binary_file_to_save_new_file()
{
var restClient = (JsonServiceClient)CreateRestClient(); // this cast was missing
var fileToUpload = new FileInfo(#"D:/test/test.avi");
var beforeHash = this.Hash(fileToUpload);
var response = restClient.PostFileWithRequest<FilesResponse>("files/UploadedFiles/", fileToUpload, new TestRequest() { Echo = "Test"});
var uploadedFile = new FileInfo(FilesRootDir + "UploadedFiles/test.avi");
var afterHash = this.Hash(uploadedFile);
Assert.That(beforeHas, Is.EqualTo(afterHash));
}
private string Hash(FileInfo file)
{
using (var md5 = MD5.Create())
{
using (var stream = file.OpenRead())
{
var bytes = md5.ComputeHash(stream);
return BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLower();
}
}
}
None of the old API was removed from the C# Service Clients, only new API's were added.
The way you process an uploaded file inside a service also hasn't changed.
I can't seem to find a way to use proxies with username and password (http/socks4). Any input would be great :)
I 'm using the .net wrapper, but I guess that does not make any difference.
Thanks,
John
You need to handle the WebControl LoginRequest Event, that is if you
want to specify the user name an password in code
private void webcontrol_LoginRequest (object sender, LoginRequestEventArgs e)
{
e.Username = "username";
e.Password = "password";
e.Handled = EventHandling.Modal;
e.Cancel = false;
}
Test:
WebPreferences prefs = new WebPreferences() { ProxyConfig = "xxx.xxx.xxx.xxx:port" };
session = WebCore.CreateWebSession(prefs);
webcontrol = new WebControl() { WebSession = session };
webcontrol.LoginRequest += new LoginRequestEventHandler(webcontrol_LoginRequest);
if you don't want to handle the event then you'll get a dialoge that
you can enter the credintials in.