Authenticate to SharePoint Online C# - c#

I'm trying to connect to SharePoint online in a console App and print the title of the site.
Its giving me the error : "The sign-in name or password does not match one in the Microsoft account system."
I have checked and made sure the username and password are 100% right.
I dont know what else to check
Heres my code:
private static void SPCredentialsConnect()
{
const string SiteUrl = "https://tenant.sharepoint.com/sites/mysite";
const string pwd = "appPassword";
const string username = "username#tenant.onmicrosoft.com";
SecureString securestring = new SecureString();
pwd.ToCharArray().ToList().ForEach(s => securestring.AppendChar(s));
ClientContext context = new ClientContext(SiteUrl);
context.Credentials = new SharePointOnlineCredentials(username, securestring);
try
{
var web = context.Web;
context.Load(web);
context.ExecuteQuery();
Console.WriteLine($"web title: {web.Title}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

Have your issue fixed? “The sign-in name or password does not match one in the Microsoft account system” Error will occur sometimes and fixed after a while with nothing changed.
AppOnly Authentication for sharepointonline can't be registed in Azure Active Directory.
It should be register in
https://contoso.sharepoint.com/_layouts/15/appregnew.aspx
And grant permission in
https://contoso-admin.sharepoint.com/_layouts/15/appinv.aspx
You can refer to following document
https://learn.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azureacs

Consider using the PnP.Framework (a NuGet package), and use the AuthenticationManager object for SPO sites. This method bypasses MFA (which is mandatory in our organization, FWIW). You can find a lot more information and examples here, including steps on getting the client id and client secret for a site. Here is what we use to log into SPO sites:
using (ClientContext context =
new AuthenticationManager().GetACSAppOnlyContext(SiteUrl, clientID, clientSecret))
{
...
}
Also, once you connect, you should adjust the Context.Load to grab the title if you want to use that value right away. Here's what I used in my code:
context.Load(web, p => p.Id, p => p.Title);
context.ExecuteQuery();
Console.WriteLine($"Logged into source {web.Title} ({web.Id})");
Good luck!
Steve in Spain

Related

Token failed : 401 Forbidden Error connecting to Sharepoint using pnpframework

I am writing a web app that I would like to have access to Sharepoint Document Libraries from a particular site using the currently logged on user credentials. I have looked at a number of articles that suggest using the PnP Framework and using a certificate instead of the client/secret ids.
I have tried both, the code of which is below:
string siteCollectionURL = "https://mycompanyname.sharepoint.com/sites/staffportal";
var authManager = new AuthenticationManager(ApplicationId, "C:\\pathtopfxfile\certifcatefx.pfx", "certificatepassword", "https://mycompany.sharepoint.com/sites/staffportal");
using (var clientContext = authManager.GetACSAppOnlyContext(siteCollectionURL,ApplicationId,ClientSecretId))
{
clientContext.Load(clientContext.Web, p => p.Title);
clientContext.ExecuteQuery();
return Ok(clientContext.Web.Title);
}
Unfortunately on the ExecuteQuery line I am consistently getting the 401 error, indicating that I am not authorized.
I registered the app in Azure -> Enterprise applications:
I have checked the following articles:
How to use the PnP Framework in a c# application
Secure Authentication of SharePoint with PnP Framework with C#(Code)
And tried the code snippets, but I cannot seem to find anything that suggests using the currently logged in user to the Web app.(see screen shot) - the user is a global administrator
Below is the error:
Any suggestions would be much appreciated.
Regards,
Darren
UPDATE TO ARTICLE
jimas13's link is what pointed me in the right direction. The tweaks I mentioned in the comment I have posted below for anyone wanting to write an MVC C# web app. This does require that the app needs to be registered and needs a self-signed certificate setup.
The two Async lines need to be written as follows:
public static async Task<AuthenticationResult> GetToken(IConfidentialClientApplication app, string[] scopes)
{
return await app.AcquireTokenForClient(scopes).ExecuteAsync();
}
public static async Task<Web> GetClientContext(string Uri,AuthenticationResult authResult)
{
using (var clientContext = ContextHelper.GetClientContext(Uri, authResult.AccessToken))
{
Web web = clientContext.Web;
clientContext.Load(web);
await clientContext.ExecuteQueryAsync();
return web;
}
}
The rest of the code is here:
public IActionResult Index()
{
AuthenticationConfiguration.AuthenticationConfiguration config = AuthenticationConfiguration.AuthenticationConfiguration.ReadFromJsonFile("appsettings.json");
string siteURL = config.SiteUrl;
string[] scopes = new string[] { config.Scope };
CertificateDescription certificate = config.Certificate;
ICertificateLoader certificateLoader = new DefaultCertificateLoader();
certificateLoader.LoadIfNeeded(certificate);
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithCertificate(certificate.Certificate)
.WithTenantId(config.Tenant)
.WithAuthority(config.Authority)
.Build();
AuthenticationResult result = GetToken(app, scopes).Result;
Web WebSite = GetClientContext(siteURL, result).Result;
return Ok(WebSite.Title);
}
The .SiteUrl and .Scope were added to the AuthenticationConfiguration.cs file as a property and then also added to the appsettings.json file.

Sending mail with EWS and OAuth 403 error

A while ago I wrote in C# a simple utility method that was called by an application to send emails automatically.
The application used to authenticate in EWS with Basic authentication (username + password) and everything worked fine.
Starting from September 2022 Microsoft started disabling this now deprecated authentication method, so I decided to update this utility method with an OAuth system autentication.
The method is the following:
public static void SendMail(string to, string cc, string bcc, string replyTo, string from, string subject, bool isHtmlFormat, string body, string[] attachments)
{
var cca = ConfidentialClientApplicationBuilder.Create("my-app-id")//app id
.WithClientSecret("my-client-secret") //Client secret
.WithTenantId("my-tenant-id") //Id tenant
.Build();
var authResult = cca.AcquireTokenForClient(new string[] { "https://outlook.office365.com/.default" }).ExecuteAsync().Result;
string[] recipients = to.Replace(" ", "").Split(';');
string[] repliesTo = string.IsNullOrWhiteSpace(replyTo) ? Array.Empty<string>() : replyTo.Replace(" ", "").Split(';');
string[] ccs = string.IsNullOrWhiteSpace(cc) ? Array.Empty<string>() : cc.Replace(" ", "").Split(';');
string[] bccs = string.IsNullOrWhiteSpace(bcc) ? Array.Empty<string>() : bcc.Replace(" ", "").Split(';');
ExchangeService service = new ExchangeService
{
Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx"),
Credentials = new OAuthCredentials(authResult.AccessToken),
ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, from)
};
service.HttpHeaders.Add("X-AnchorMailbox", from); //Include x-anchormailbox header
EmailMessage emailMessage = new EmailMessage(service)
{
From = new EmailAddress(from),
Subject = subject,
Body = new MessageBody(isHtmlFormat ? BodyType.HTML : BodyType.Text, body)
};
emailMessage.ToRecipients.AddRange(recipients);
emailMessage.ReplyTo.AddRange(repliesTo);
emailMessage.CcRecipients.AddRange(ccs);
emailMessage.BccRecipients.AddRange(bccs);
foreach (string attachment in attachments ?? Array.Empty<string>())
{
emailMessage.Attachments.AddFileAttachment(attachment);
}
emailMessage.Send();
}
The function is called in a very straightforward way:
MailHelper.SendMail("MyEmail#myCompany.com", null, null, null, "NoReply#myCompany.com", "Test subject", false, "This is a test body", null);
The problem is that as soon as the Method emailMessage.Send(); is called, a Microsoft.Exchange.WebServices.Data.ServiceRequestException reporting a 403 forbidden error is thrown.
I already registered the app in the Azure Active Directory interface, set a secret and set the following permissions:
The accounts in my tenant are set to allow Exchange services:
I already double checked IDs and account names to be sure it was not a trivial mistake, but I'm not an expert when it comes to EWS, so I'm surely missing something, unfortunately I don't know where.
Thanks id advance.
The error 403 Forbidden usually occurs if you don't have required permissions or missed granting admin consent to the added API permissions.
Please note that, the code you mentioned is using "Client credentials flow" that works with only Application permissions but you added all Delegated permissions.
In that case, you will get 403 Forbidden error even you granted consent to Delegated permissions.
I tried to reproduce the same in my environment via Postman and got the below results:
I created one Azure AD application and added API permissions same as you like below:
Now, I generated an access token using "Client credentials flow" via Postman like below:
POST https://login.microsoftonline.com/tenantID/oauth2/v2.0/token
client_id:appID
grant_type:client_credentials
client_secret:secret
scope:https://graph.microsoft.com/.default
Response:
When I used the above token to send mail with below query, I got 403 Forbidden error like this:
POST https://graph.microsoft.com/v1.0/users/userID/sendmail
Response:
To resolve the error, you need to add Application permissions and grant admin consent to them like below:
Now I generated the access token again and used it in running below query to send sample mail:
In your case, add Application permissions by granting admin consent to them and run the code again.

Google API authentification Server-Side

I'm trying to use the Gmail API reading the emails, but I'm running into the problem that I want to do a server-side authentification but with all the examples from google Doc., he always shows me window asking me to add my credentials (Gmail & password).
public static async void CreateService()
{
GoogleCredential credential;
using (var stream = new FileStream(#"key.json", FileMode.Open,
FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(GmailService.Scope.GmailLabels,
GmailService.Scope.GmailModify, GmailService.Scope.GmailMetadata,
GmailService.Scope.GmailReadonly);
}
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Gmail",
});
Console.WriteLine(ListMessages(service, "me", ""));
}
Then I got this code from the documentation of the google api of how to read the messages from a user.
public static List<Message> ListMessages(GmailService service, String userId, String query)
{
List<Message> result = new List<Message>();
UsersResource.MessagesResource.ListRequest request = service.Users.Messages.List(userId);
request.Q = query;
do
{
try
{
ListMessagesResponse response = request.Execute();
result.AddRange(response.Messages);
request.PageToken = response.NextPageToken;
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
} while (!String.IsNullOrEmpty(request.PageToken));
return result;
}
But when I run it I get this error: An error occurred:
Google.Apis.Requests.RequestError
Bad Request [400]
Errors [
Message[Bad Request] Location[ - ] Reason[failedPrecondition] Domain[global]
]
Answer:
If you want to use a user as the authentication account, then no. This is not possible and you will always get a login window pop-up.
Other Methods:
You can however create and use a service account to impersonate your user and bypass the need for authenticating on run. They require a little extra set up but you can create them in the Google Developer Admin Console.
Code Example:
After creating your service account and giving it the roles and permissions it needs (see links below), you only need to make small edits to your code to use it instead of your regular account. This is an example in Python, but you can find other examples on the managing keys page:
import os
from google.oauth2 import service_account
import googleapiclient.discovery
def create_key(service_account_email):
"""Creates a key for a service account."""
credentials = service_account.Credentials.from_service_account_file(
filename=os.environ['GOOGLE_APPLICATION_CREDENTIALS'],
scopes=['YOUR SCOPES HERE'])
# Rememer here that your credentials will need to be downloaded
# for the service account, YOU CAN NOT USE YOUR ACCOUNT'S CREDENTIALS!
service = googleapiclient.discovery.build(
'service', 'version', credentials=credentials)
key = service.projects().serviceAccounts().keys().create(
name='projects/-/serviceAccounts/' + service_account_email, body={}
).execute()
print('Created key: ' + key['name'])
References:
Google Developer Admin Console
Google Cloud - Service Accounts
Understanding Service Accounts
Creating and Managing Service Accounts
Creating and Managing Service Account Keys
Granting Roles to Service Accounts

Clear Credentials stored in Universal Windows App

I'm using Windows.Web.Http.HttpClient in Universal Windows platform (UWP). The URL needs domain credentials (NTLM) so windows opens a self defined popup for username and password. App needs a functionality to logout but I couldn;'t find a working code which can clear credentials stored by UWP.
I have tried to clear credentials from Windows.Security.Credentials.PasswordVault using following code but it didn't work:
var cred = new Windows.Security.Credentials.PasswordVault();
var pwds = cred.RetrieveAll();
foreach(var pwd in pwds)
{
pwd.RetrievePassword();
cred.Remove(pwd);
}
I'm also clearing cookies as below:
var filter = new HttpBaseProtocolFilter();
var cookieManager = filter.CookieManager;
HttpCookieCollection cookies = cookieManager.GetCookies(uri);
foreach (HttpCookie u in cookies)
{
cookieManager.DeleteCookie(u);
}
Any suggestions please?
This isn't available in Windows 10, but will be in the Anniversary Update:
var filter = new HttpBaseProtocolFilter();
filter.ClearAuthenticationCache();
You can see more on the MSDN page and if you have an Insider Preview build / SDK later than 14295 you should be able to test it.
Please look at:
https://learn.microsoft.com/en-us/windows/uwp/security/credential-locker#deleting-user-credentials
There function for deleting the credentials is described.
It seems that the method public IReadOnlyList<PasswordCredential> RetrieveAll() that you are using returns a read-only collection. Therefor its values can't be deleted.
Try to access the credentials e.g. with public PasswordCredential Retrieve(String resource, String userName). The return type which is not read-only, should enable you to use the delete methods.
If you want to delete all credentials for a specific resource name, this workaround works even in older Windows 10 versions:
private void RemoveAllCredentials(PasswordVault passwordVault)
{
//Get all credentials.
List<PasswordCredential> passwordCredentials = new List<PasswordCredential>();
var credentials = passwordVault.RetrieveAll();
foreach (PasswordCredential credential in credentials)
{
if (credential.Resource.Equals("ResourceName"))
{
passwordCredentials.Add(
passwordVault.Retrieve(credential.Resource, credential.UserName));
}
}
foreach (PasswordCredential entry in passwordCredentials)
{
passwordVault.Remove(entry);
}
}

Using Kerberos Tokens in C#

I'm trying use Kerberos tokens directly in C# using the KerberosSecurityTokenProvider. Unfortunately, the documentation on its use is very limited and I've not had much success. I've written the following sample test application:
var oProvider = new KerberosSecurityTokenProvider("MACHINENAME",TokenImpersonationLevel.Identification);
var oToken = (KerberosRequestorSecurityToken)oProvider.GetToken(TimeSpan.FromHours(1));
Console.WriteLine(oToken.ValidFrom);
Console.WriteLine(oToken.ValidTo);
Console.WriteLine(oToken.Id);
var abRequest = oToken.GetRequest();
var sId = oToken.Id;
try
{
var oReceivedToken = new KerberosReceiverSecurityToken(abRequest, sId);
var oAuthenticator = new KerberosSecurityTokenAuthenticator();
var oCol = oAuthenticator.ValidateToken(oReceivedToken);
foreach (var o in oCol)
{
Console.WriteLine(o.Id);
}
}
catch(Exception e)
{
Console.WriteLine(e);
}
Where MACHINENAME is the name of my machine. It successfully gets a Kerberos Token, but when I try to validate it, I get:
System.IdentityModel.Tokens.SecurityTokenException: The AcceptSecurityContext failed. ---> System.ComponentModel.Win32Exception: The logon attempt failed
Which leaves me with a number of questions:
Is this the correct way to get/validate Kerberos Tokens in C#?
Why is it trying to perform a login if I am just saying I want to use the token for identification?
Is the error due to my code, or are there domain configuration issues that also need to be addressed?
Any comments on how to use your own Kerberos tokens in .NET?

Categories