My Question is about Active Directory (AD), Active Directory Federation Services (ADFS), Single Sing On (SSO) and SAML.
We have a Client/Server Application with the following Specs:
- Client: WPF .NET Native Client based on .NET 4.7.2 and C#
- Server: REST-Service based on Java Spring
One main Requirement is SSO with AD/ADFS.
In Best Case the User should be authenticated seamlessy/silent.
The main Restriction is AD and ADFS based on Windows Server 2012 R2.
In the following Picture you can how we planned to implement SSO with ADFS.
SSO-Scenario
The .NET Native Client tries to use the REST-Service without Authentication.
The REST-Service redirects the .NET Native Client to ADFS-Server.
The .NET Native Client tries to get a SAML-Token with the current logged on User-Credentials(Windows Logon).
If the current User ist granted the ADFS-Server respond with and SAML-Token.
The .NET Native Client takes the SAML-Token and passes it to the REST-Service.
If the SAML-Token is accepted the User Access is granted.
If the SAML-Token is not accepted the User should get an Login-Screen in the App.
At this time im totally confused about SSO with ADFS in .NET Native Client.
I can't find any suitable Demos etc.
Many Demos or Use Cases are about ASP.NET but almost nothing about Native Clients.
I'm starting to wonder if my assumption about SSO with AD/ADFS and SAML aren't true.
I started building an AD-ADFS-Lab with Virtual Machines described in Understanding ADFS an Introduction to ADFS.
Then I'm trying to play with the ADFS-Server but couldn't get it done.
I was looking at this Libraries:
ADAL.NET
MSAL
After 1 week of Research my Head is spinning:
Do I need WIF?
What about WCF?
I know there is OAuth. I know Windows Server 2012 isn't the lastest and best for this Scenario. But the Requirements and Restrictions come direct from our Customer.
What can I do?
What can I read or try?
Are thre any other Libraries?
Are there any Examplex?
Update
I was able to "talk" to my ADFS-Server with WS-Trust and get a SAML-Token.
private static void Main(string[] args)
{
string adfs = "https://ad-fs.adlab.local";
string adfsEndpoint = "https://ad-fs.adlab.local/adfs/services/trust/13/usernamemixed";
string appServer = "https://ad-server.adlab.local/sampapp/";
var factory = new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential), adfsEndpoint);
factory.TrustVersion = TrustVersion.WSTrust13;
var channelCredentials = factory.Credentials;
channelCredentials.UserName.UserName = "Administrator#adlab";
channelCredentials.UserName.Password = "SsoLab2019";
channelCredentials.SupportInteractive = false;
RequestSecurityToken rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference(appServer),
KeyType = KeyTypes.Bearer
};
var channel = factory.CreateChannel();
try
{
var token = (GenericXmlSecurityToken)channel.Issue(rst);
Console.Write(token.TokenXml.OuterXml);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
No I have to figure out how I can get the SAML-Token silent/seamless.
Related
I want to interact with OneDrive in my WinForms application. Sadly, the Azure quick start samples do not include WinForms, just UWD.
The flow on what I have to do is consistent, namely given my Client ID, I have to obtain an Authentication Code. Given the authentication code, I can then obtain an Access Code, which will allow me to interact in a RESTful way with the OneDrive API. My plan is to have the authentication piece go in a .Net Framework Library and the file IO calls will go in another library that has no user interface access, as it will go in a Windows Service. I would pass the Access Token to the service.
AADSTS50059: No tenant-identifying information found in either the request or implied by any provided credentials.
This error corresponds to the following code fragment that I lifted from the sample .Net Core daemon quick start code.
Note: I was playing around with Scopes as I kept receiving scope errors and I saw one article, whose link I should have kept, which stated to use the API and default scope.
public bool GetRestAuthenticationToken(out string tokenAuthentication)
{
tokenAuthentication = null;
try
{
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create(Authenticate.AppClientId)
.WithClientSecret(Authenticate.AppClientSecret)
.WithAuthority(new Uri(#"https://login.microsoftonline.com/common/oauth2/nativeclient"))
.Build();
string scope = $"onedrive.readwrite offline_access";
System.Collections.Generic.List<string> enumScopes = new System.Collections.Generic.List<string>();
enumScopes.Add("api://<GUID>/.default");
//enumScopes.Add(Authenticate.Scopes[1]);
var result = Task.Run(async () => await app.AcquireTokenForClient(enumScopes).ExecuteAsync()).Result;
...
}
...
}
I believe that I have my application configured properly now on Azure, but am not 100% positive.
API Permissions:
Authentication:
Desktop Applications: https://login.microsoftonline.com/common/oauth2/nativeclient
Desktop Applications: https://login.live.com/oauth20_desktop.srf
Implicit Grants: Access tokens & ID tokens
Live SDK support (Yes)
Default client type (Yes)
Others:
I do have a client secret and kept note of all the Overview GUIDs
Microsoft Doc 1
I tried several different URLs, but only the one not commented out works with the fragment above, but throws the referenced error.
//string redirect_uri = #"https://www.myapp.com/auth";
//string redirect_uri = "https://login.live.com/oauth20_desktop.srf";
string url = #"https://login.microsoftonline.com/common/oauth2/nativeclient";
//string url = $"https://login.live.com/oauth20_authorize.srf?client_id={appClientId}&scope={scope}&response_type=code&redirect_uri={redirect_uri}";
//string url = $"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" +
// $"client_id={Authenticate.AppClientId}&" +
// $"scope={scope}&" +
// $"response_type=token&" +
// $"redirect_uri={redirect_uri}";
The goal is the same, namely to obtain an access token that I can use with RESTful calls to work with files and/or directories on OneDrive, e.g.
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.GetAsync(...);
You are trying to implement Client credentials grant type to get the access token.
Based on MSAL initialization, Authority is
(Optional) The STS endpoint for user to authenticate. Usually
https://login.microsoftonline.com/{tenant} for public cloud, where
{tenant} is the name of your tenant or your tenant Id.
We assume that your tenant is "myTenent.onmicrosoft.com", then you should set it as https://login.microsoftonline.com/myTenent.onmicrosoft.com here.
I notice that you specify a scope "onedrive.readwrite" in your code. But it's not a valid permission of Microsoft Graph. The default scope of Microsoft Graph is https://graph.microsoft.com/.default.
My question today is:
How to configure HttpClient so that it can authenticate the call without bothering the user on MacOS?
(.NET Core 2.2 console app running as Launch Agent on MacOS, calling a Web API on IIS with NTLM and Kerberos enabled, over our company's internal network)
Long story:
I have a .NET Core app that uses the following method to call a web api:
var handler = new HttpClientHandler()
{
UseDefaultCredentials = true
};
var client = new HttpClient(handler)
{
BaseAddress = new Uri("https://MyWebAPI.MyCompanyName.com/")
};
string result = client.GetAsync("MyEndpointSubURL")
.Result.Content.ReadAsStringAsync().Result;
When I run this on my Windows machine, the app easily connects and gets the result.
However, when I run this on a Mac, I get an exception:
Interop+NetSecurityNative+GssApiException - GSSAPI operation failed with error
The provided name was not a mechanism name. (unknown mech-code 0 for mech unknown).
at Microsoft.Win32.SafeHandles.SafeGssNameHandle.CreatePrincipal(String name)
Any ideas what I need to change to make it work?
We desperately want to avoid bothering the user with prompts (it's meant to be a background syncing service).
Recall, it's a .NET Core 2.2 console app running as Launch Agent on MacOS. The Web API it's calling is an Asp.NET Web API hosted with IIS with NTLM and Kerberos enabled and I only need to get past IIS (web API does not use any authentication/authorization mechanisms by itself). The API is exposed only over our company's internal network, so the user is already logged in to the network.
Try running kinit <username>#<DOMAIN> from the terminal and then running your program again. You may need to configure your krb5.conf file to properly point to the domain controller.
We have "default credentials" working in our system on Mac w/ .NET Core 2.1+ using the same code you show there. Configuring Kerberos through kinit and the conf file is the biggest challenge.
Based on what I can tell, .NET doesn't use the cache produced from running kinit, but this is what configures the principal to be used. .NET's interaction with Kerberos is poorly documented. See https://github.com/dotnet/corefx/issues/30203#issuecomment-395592407
I had a very hard time getting this to work on macOS with .NET Core 2.2.
I followed the online documentation about setting up your krb5.conf, running kinit and klist to make sure I had a valid kerberos ticket.
After all that, kerberos was working with Azure Data Studio, so I knew my setup was okay, but I could not get HttpClient with UseDefaultCredentials = true working. It always failed with the same error:
System.ComponentModel.Win32Exception: GSSAPI operation failed with error - An unsupported mechanism was requested (unknown mech-code 0 for mech unknown).
It did however work on a coworker's machine.
After a lot of digging, we discovered my coworker had .NET Core 2.2.7 installed while I only had 2.2.1. Upgrading my workstation to .NET Core 2.2.8 resolved my issue. Also rolling back our app to use 2.1.13 worked as well.
I hope this helps someone else in the future.
Try this:
With basic auth example.
var url = "https://MyWebAPI.MyCompanyName.com/";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "Base64Credetials");
using (var response = await httpClient.GetAsync(url))
if (response.IsSuccessStatusCode)
{
var strResponse = await response.Content.ReadAsStringAsync();
MyObject result = JsonConvert.DeserializeObject<MyObject>(strResponse);
if (result != null)
{
//Your code here
}
}
}
I don't think MacOS has a concept of "default authentication" in the same way Windows does. Kerberos and NTLM are both Windows concepts. I suspect on MacOS you will have to use a different authentication scheme (Basic or Bearer) and then retrieve the credentials from somewhere such as the Keychain. IIRC an app can be granted silent read access to the key chain.
I need to push calendar entries in to a client's Outlook account. This is fairly straight forward with Exchange. You just authenticate with a user that has access, and then you can push entries in to other user's accounts. It seems to be completely different in Office 365.
I tried to follow the instructions here:
https://dev.outlook.com/restapi/getstarted
I created the app and got the app's client ID. But, all of the documentation is around oAuth. Generally speaking, oAuth is designed for scenarios when a user needs to enter their credentials in through a browser window that will then confirm with the user which credentials they are willing to allow the app to have.
This does not match my scenario. I need to be able to push the calendar entries in to the account without any UI. This is back end integration. It just needs to do its job silently.
I looked at this sample app:
https://github.com/OfficeDev/O365-Win-Snippets
But, this is a front end app. When it needs to authenticate, it pops up a window to force the user to enter their credentials.
When I try to call the REST API that is mentioned in the getting started page, it returns HTML. This is the Url it mentions:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F&response_type=code&scope=https%3A%2F%2Foutlook.office.com%2Fmail.read
I've tried a few permutations of this Url with my client ID. I've tried passing in my Office 365 credentials through basic http authentication.
I'm stuck.
The answer is simple. Use the Exchange API - not Office 365 API.
I was confused because I assumed that Office 365 was a different entity to Exchange, but the Office 365 email server just is one giant Exchange server. Here's some sample code for good measure. This is an example of logging in to Office 365's Exchange server and sending off a calendar entry to an email address. Simple.
I made a wild guess about the exchange Url and it was correct:
https://outlook.office365.com/ews/exchange.asmx
//Connect to exchange
var ewsProxy = new ExchangeService(ExchangeVersion.Exchange2013);
ewsProxy.Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
//Create the meeting
var meeting = new Appointment(ewsProxy);
ewsProxy.Credentials = new NetworkCredential(_Username, _Password);
meeting.RequiredAttendees.Add(_Recipient);
// Set the properties on the meeting object to create the meeting.
meeting.Subject = "Meeting";
meeting.Body = "Please go to the meeting.";
meeting.Start = DateTime.Now.AddHours(1);
meeting.End = DateTime.Now.AddHours(2);
meeting.Location = "Location";
meeting.ReminderMinutesBeforeStart = 60;
// Save the meeting to the Calendar folder and send the meeting request.
meeting.Save(SendInvitationsMode.SendToAllAndSaveCopy);
My understanding is that this is possible, but the authentication looks quite complicated. For starters, any application that requires Office 365 integration must also integrate with the associated Azure AD. You can register your application for specific users so that it has the permissions required for whatever operations you need to perform. See here for a good summary of this component: https://msdn.microsoft.com/en-us/office/office365/howto/connect-your-app-to-o365-app-launcher?f=255&MSPPError=-2147217396#section_2
For authentication, you require a daemon/server application model. I've not attempted this yet, but it's documented here and looks like it should meet your needs (see the Daemon or Server Application to Web API section): https://azure.microsoft.com/en-us/documentation/articles/active-directory-authentication-scenarios/#daemon-or-server-application-to-web-api
In order to call the Office 365 REST API, the app requires an access token from Azure Active Directory, that's why you need (mandatory) to register app in Microsoft Azure Active Directory (Azure AD). Your Office 365 account in turn needs to be associated with Azure AD. This answer summarizes on how to register app in Azure AD in order to consume Office 365 API.
Basic authentication scheme
Regrading Basic authentication, currently it is enabled for API version 1.0, the following example demonstrates how to consume Outlook Calendar REST API in .NET application.
Prerequisites:
domain: https://outlook.office365.com/
API version: v1.0
Here is an example that gets my calendars and prints its names
private static async Task ReadCalendars()
{
var handler = new HttpClientHandler();
handler.Credentials = new NetworkCredential()
{
UserName = ConfigurationManager.AppSettings["UserName"],
Password = ConfigurationManager.AppSettings["Password"]
};
using (var client = new HttpClient(handler))
{
var url = "https://outlook.office365.com/api/v1.0/me/calendars";
var result = await client.GetStringAsync(url);
var data = JObject.Parse(result);
foreach (var item in data["value"])
{
Console.WriteLine(item["Name"]);
}
}
}
I have a problem. I use Azure AD to authenticate my asp.net app. Authentication works fine. Then I from this app trying to access OneDrive for Business using sharepoint search rest api. But the server always receives a response with a 401 error. I understand that the problem is in the access token which I use (Now I use the token received from Azure AD). But I never found the normal description of how to obtain an access token for the sharepoint search rest api.
Thanks in advance
Answer
You need to give your ASP.NET Application permission to use your OneDrive for Business application.
Here is an overview of how to do this using the Azure Management Portal. (Note that your OneDrive for Business account is a type of Office 365 SharePoint Online account.)
Go to manage.windowsazure.com > Active Directory > Your Tenant. If your tenant has an associated OneDrive for Business account, then its list of applications will include Office 365 SharePoint Online.
If your tenant's list of application does include Office 365 SharePoint Online, then your next step is to give your ASP.NET Web Application permission to access it.
Open up your Web Application's page in the Azure Active Directory area. Then choose CONFIGURE > Add Application. Add the Office 365 SharePoint Online application. Give it all necessary permissions and save.
The following screenshot is for a Native Client Application, because that is what my demo code is using. You can do a similar thing for a Web Application, though you will need to use an X509 Certificate for authentication instead of a username/password.
Your access token will now work with your Office 365 for Business account. Hooray!
Demo
Here is some sample code that works on my machine with a Native Client App. You can do the same thing with a Web Application, though you will need to use an X509 Certificate instead of a username/password.
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net;
namespace AAD_SharePointOnlineApp
{
class Program
{
static void Main(string[] args)
{
var authContext =
new AuthenticationContext(Constants.AUTHORITY);
var userCredential =
new UserCredential(Constants.USER_NAME, Constants.USER_PASSWORD);
var result = authContext
.AcquireTokenAsync(Constants.RESOURCE, Constants.CLIENT_ID_NATIVE, userCredential)
.Result;
var token = result.AccessToken;
var url = "https://mvp0.sharepoint.com/_api/search/query?querytext=%27timesheets%27";
var request = WebRequest.Create(url);
request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
var response = request.GetResponse() as HttpWebResponse;
}
}
class Constants
{
public const string AUTHORITY =
"https://login.microsoftonline.com/mvp0.onmicrosoft.com/";
public const string RESOURCE =
"https://mvp0.sharepoint.com";
public const string CLIENT_ID_NATIVE =
"xxxxx-xxxx-xxxxx-xxxx-xxxxx-xxxx";
public const string USER_NAME =
"MY_USER#mvp0.onmicrosoft.com";
public const string USER_PASSWORD =
"MY_PASSWORD";
}
}
Comments
If you are trying to do the above with a Web Application instead of a Native Client Application, then you will need to use an X509 Certificate, otherwise you will receive the following error.
Unsupported app only token.
See also: http://blogs.msdn.com/b/richard_dizeregas_blog/archive/2015/05/03/performing-app-only-operations-on-sharepoint-online-through-azure-ad.aspx
Are there any libraries out there for C# that wrap the process of sharing moments to a user's Google+ account (or to their stream)? I'm looking for something that simply take your ClientId and ClientSecret, and maybe your apiKey along with the user's id to send some text that the user has decided to share with his/her friends.
If not, but you have an example of creating a WebRequest to accomplish the same thing, that would be much appreciated too!
I've reviewed this landing page: https://developers.google.com/+/quickstart/csharp
But I'm trying to integrate into an existing MVC5 application that already has the Auth for GooglePlus taken care of.
The correct client to be using for Google APIs is the Google .NET API Client library, available via NuGet. Additional libraries for specific APIs are required if you use more than the core library. For Plus, you need the Google.Apis.Plus.v1 package.
After you have added it to your projects and have configured an API client, writing app activities is as easy as:
/// <summary>The app activity type for ADD.</summary>
private const string ADD_ACTIVITY_TYPE = #"http://schemas.google.com/AddActivity";
// Construct your Plus Service, I'll assume a helper for here.
PlusService plusService = GetPlusService(credentials);
Moment addMoment = new Moment();
ItemScope target = new ItemScope()
{
Url = ContentUrl
};
addMoment.Type = ADD_ACTIVITY_TYPE;
addMoment.Target = target;
Moment response = null;
try
{
response = plusService.Moments.Insert(addMoment, "me",
MomentsResource.InsertRequest.CollectionEnum.Vault).Execute();
}
catch (System.AggregateException)
{
/* Occurs when the server can't be seen by Google. */
}
catch (Google.GoogleApiException)
{
/* Occurs when the server can't be seen by Google. */
}
How to authenticate a user and authorize your client for access to Google APIs in MVC can be found on this blog: ASP.NET MVC with OpenID and OAuth.
A final note, app activities require you to specify an app activities pseudo-scope (request_visible_actions) which is easier with the Sign-In button than via the framework. If you are getting 401 errors, this is the most likely culprit.