System.Threading.ThreadStateException in ASP.NET MVC 5 when Acquire Token from WAAD - asp.net-mvc

I'm implementing the following scenario: ASP.NET MVC 5 application access OData WebAPI with Azure Active Directory authentication (like in this article: http://msdn.microsoft.com/en-us/magazine/dn463788.aspx ).
However, when I call AuthenticationContext.AcquireToken I get System.Threading.ThreadStateException saying: ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded
apartment.
EDITED:
Steps to reproduce:
Create New MVC project with Organizational Authentication. Use your Windows Azure Domain and MSDN Account
Add Actice Directory Authentication Library via NuGet
Add action with the following code:
public async Task<ActionResult> Index(){
AuthenticationContext ac = new AuthenticationContext("https://login.windows.net/domain.onmicrosoft.com");
AuthenticationResult ar = ac.AcquireToken("https://domain.onmicrosoft.com/WindowsAzureADWebAPITest",
"a4836f83-0f69-48ed-aa2b-88d0aed69652",
new Uri("https://domain.onmicrosoft.com/myWebAPItestclient")
);
// Call Web API
string authHeader = ar.CreateAuthorizationHeader();
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://server.com:44353/api/Values");
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
return View();
}
Run the code and reproduce the issue (AcqureToken method call).
Please suggest a fix.
Thank you!

That particular overload of AcquireToken() is only usable in a native client app because the way it handles user authentication is by opening a browser window to login.windows.net. This requires the app to host a browser ActiveX control and that's why it needs an STA thread.
Now, in your example the code runs inside IIS on the server machine where hosting ActiveX controls is just not possible.
What you really need is delegation which is described here: http://www.cloudidentity.com/blog/2013/10/29/using-adals-acquiretokenby-authorizationcode-to-call-a-web-api-from-a-web-app/
Same author, just the different article.

Related

How I can call an api from MVC .net 4.7.2 using Microsoft Identity Planform (Azure AD

I am following a tutorial from microsoft docs and I have created an api with Microsoft Identity Platform using Azure AD in asp.net core 5.
The tutorialI followed shows how to call an api from asp.net core 5, and I have done that part but now I want to call the api from asp.net 4.7.2. Since I am new to apis and example I am finding are not using Microsoft Identity platform to call an api secured by microsoft identity
Can someone point me to document, tutorial, or code which shows me how I can call the api. Code should be written in asp.net not core.
I have done some part but stuck on calling the api.
See the below code
Api methods:
I have already setup the api and web app in Azure portal and configured permission to 2 of the scope.
Method in api.
GetCategory()
GetCatalog()
private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification
notification)
{
notification.HandleCodeRedemption();
var idClient = ConfidentialClientApplicationBuilder.Create(clientId)
.WithRedirectUri(redirectUri)
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.Build();
var signedInUser = new ClaimsPrincipal(notification.AuthenticationTicket.Identity);
try
{
var apiScope = "catalog.Read, Category.Read";
string[] scopes = apiScope.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(
scopes, notification.Code).ExecuteAsync();
//rest of the code to call the api for both scope
// and if i have to do add some code to controller
Not sure if you are still looking for an answer but here it goes.
Once you get the accessToken with required scope, you just need to add it as Authorization Header when you make a call to the API:
const string Scheme = "Bearer";
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(Scheme, result.AccessToken);
var result = await httpClient.SendAsync(httpRequestMessage)

Sustainsys SAML2 Sample for ASP.NET Core WebAPI without Identity

Does anyone have a working sample for Sustainsys Saml2 library for ASP.NET Core WebAPI only project (no Mvc) and what's more important without ASP Identity? The sample provided on github strongly relies on MVC and SignInManager which I do not need nor want to use.
I added Saml2 authentication and at first it worked fine with my IdP (I also checked the StubIdP provided by Sustainsys) for first few steps so:
IdP metadata get properly loaded
My API properly redirects to sign-in page
Sign-in page redirects to /Saml2/Acs page, and I see in the logs that it parses the result successfully
However I don't know how to move forward from there and extract user login and additional claims (my IdP provided also an e-mail, and it is included in SAML response which I confirmed in the logs).
Following some samples found on the web and modyfing a little bit the MVC Sample from GitHub I did the following:
In Startup.cs:
...
.AddSaml2(Saml2Defaults.Scheme,
options =>
{
options.SPOptions.EntityId = new EntityId("...");
options.SPOptions.ServiceCertificates.Add(...));
options.SPOptions.Logger = new SerilogSaml2Adapter();
options.SPOptions.ReturnUrl = new Uri(Culture.Invariant($"https://localhost:44364/Account/Callback?returnUrl=%2F"));
var idp =
new IdentityProvider(new EntityId("..."), options.SPOptions)
{
LoadMetadata = true,
AllowUnsolicitedAuthnResponse = true, // At first /Saml2/Acs page throwed an exception that response was unsolicited so I set it to true
MetadataLocation = "...",
SingleSignOnServiceUrl = new Uri("...") // I need to set it explicitly because my IdP returns different url in the metadata
};
options.IdentityProviders.Add(idp);
});
In AccountContoller.cs (I tried to follow a somewhat similar situation described at how to implement google login in .net core without an entityframework provider):
[Route("[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
private readonly ILog _log;
public AccountController(ILog log)
{
_log = log;
}
[HttpGet("Login")]
[AllowAnonymous]
public IActionResult Login(string returnUrl)
{
return new ChallengeResult(
Saml2Defaults.Scheme,
new AuthenticationProperties
{
// It looks like this parameter is ignored, so I set ReturnUrl in Startup.cs
RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
});
}
[HttpGet("Callback")]
[AllowAnonymous]
public async Task<IActionResult> LoginCallback(string returnUrl)
{
var authenticateResult = await HttpContext.AuthenticateAsync(Constants.Auth.Schema.External);
_log.Information("Authenticate result: {#authenticateResult}", authenticateResult);
// I get false here and no information on claims etc.
if (!authenticateResult.Succeeded)
{
return Unauthorized();
}
// HttpContext.User does not contain any data either
// code below is not executed
var claimsIdentity = new ClaimsIdentity(Constants.Auth.Schema.Application);
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
_log.Information("Logged in user with following claims: {#Claims}", authenticateResult.Principal.Claims);
await HttpContext.SignInAsync(Constants.Auth.Schema.Application, new ClaimsPrincipal(claimsIdentity));
return LocalRedirect(returnUrl);
}
TLDR: Configuration for SAML in my ASP.NET Core WebApi project looks fine, and I get success response with proper claims which I checked in the logs. I do not know how to extract this data (either return url is wrong or my callback method should work differently). Also, it is puzzling why successfuly redirect from SSO Sign-In page is treated as "unsolicited", maybe this is the problem?
Thanks for any assistance
For anyone who still needs assistance on this issue, I pushed a full working example to github which uses a .Net Core WebAPI for backend and an Angular client using the WebAPI. you can find the example from here:
https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample
As it turned out, the various errors I've been getting were due to my solution being hosted inside docker container. This caused a little malfunction in internal aspnet keychain. More details can be found here (docker is mentioned almost at the end of the article):
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?tabs=aspnetcore2x&view=aspnetcore-2.2
Long story short, for the code to be working I had to add only these lines:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("/some/volume/outside/docker")); // it needs to be outside container, even better if it's in redis or other common resource
It fixed everything, which includes:
Sign-in action to external cookie
Unsolicited SSO calls
Exceptions with data protection key chain
So it was very difficult to find, since exceptions thrown by the code didn't point out what's going on (and the unsolicited SSO calls made me think that the SSO provider was wrongly configured). It was only when I disassembled the Saml2 package and tried various code pieces one by one I finally encoutered proper exception (about the key chain) which in turned led me to an article about aspnet data protection.
I provide this answer so that maybe it will help someone, and I added docker tag for proper audience.

Unable to retrieve LinkedIn Authorization Code

I'm building an app that I would use to make posts to LinkedIn. I'm using Visual Studio 2017 (asp.net mvc c#).
I have already created an app in LinkedIn with Client ID and Client Secret.
I have also create an async Function GetCode() my application in Visual Studio.
This function makes a request to this url:
string url = "https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=kjahldj9384&state=DCEEFWKDHIUs5dffef424&redirect_uri=http://localhost:51272/";
When i run the application and this function gets called, I'm redirected to the LinkedIn Authentication page to login. But when I enter the LinkedIn login credentials and hit login, it takes me to this page: https://www.linkedin.com/uas/login-submit which displays the error message:
Request Error
We’re sorry, there was a problem with your request. Please make sure you have cookies enabled and try again.
Or follow this link to return to the home page.
This is my GetCode function:
//Get LinkedIn Code
[HttpGet]
[Route("api/LinkedIn/GetCode")]
public async Task<HttpResponseMessage> GetCode()
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Content-Type", "application/x-www-form-urlencoded");
string url = "https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=54565kggfh&state=DCEEFW55754FD5dffef424&redirect_uri=http://localhost:57313/";
HttpResponseMessage apiResponseMsg = await client.GetAsync(new Uri(url));
return apiResponseMsg;
}
}
Is there something I'm doing wrong?
First, make sure to add your redirect_uri to OAuth 2.0 Settings in the Linkedin developer portal (under Auth tab). If that isn't the issue, try below.
If you build the same URL, and redirect the page to it instead of using a response message, then you should be sent back to your website with the code as a query parameter (http://localhost:60137/?code=xxxxxxxxxxxxx). I made a working example with a test application:
[HttpGet]
public ActionResult GetCode()
{
//build url from config file
string url = "https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=78z99sg1ncgbma&redirect_uri=http://localhost:60137";
return new RedirectResult(url);
}
Hope this helps.

A simple ASP .NET MVC API controller using roles

I wrote a web application using ASP .NET MVC and authorization system by default. I configured IdentityRole and input through external providers. Using the current database I have created my data context. Now I want to write a Xamarin.Android app and connect to my database, I want a simple API. But the feature that you want to access this API was only available to user with a certain role. The API is really very simple and therefore do not want to add to the draft WCF or WebAPI project. How to do it best?
First, you don't need a separate project to use Web Api; you can use both MVC and Web Api in the same project. For one off endpoints for things like in-site AJAX requests, just creating MVC actions that return JSON or XML would be fine, but if you're talking about a true API, even if it's fairly simplistic, I'd say go Web Api.
You'd protect your Web Api actions much the same as you would your MVC actions, using the [Authorize] attribute. If you need to restrict by role, you just pass a role(s) to that. However, the big difference here, especially if you're serving a mobile app, is that you'll need pass the authorization along with the request. That's generally accomplished using the Authorization header along with a bearer token. Basically, you would need to set up an endpoint that signs a user in and returns a token. Then, each subsequent request that needs authorization includes that token in the header.
I want to finish and to fully answer this question and close this topic. I've been searching for how to add the ability for a mobile client to connect to an existing site on ASP.NET MVC. In my search, I came across a great article Justin Hyland on March 2, 2014
In principle, everything in this article is well and clearly written, but I want to make a tiny contribution for clarity.
Under Setup WebAPIConfig stated that the need
added in the following code to the WebApiConfig Register method
But if we consider the case ASP.NET MVC we don't have such file. It's all very simple, you just need such a file to create the folder App_Start. The contents of the file can be left exactly as it is in the article.
To get rid of the bugs which will inevitably appear we need to install two nuget package: Microsoft.AspNet.WebApi and Microsoft.AspNet.WebApi.Owin.
Excellent! Now we can turn to the method to obtain the token and then adding the token to the query we can get the needed data closed by the attribute [Authorize].
A small remark. If You need to access a method which is closed for a specific role that to the Authenticate method from the article should add a few lines of code. Immediately after the line:
identity.AddClaim(new Claim(ClaimTypes.Name, user));
add the line:
identity.AddClaim(new Claim(ClaimTypes.Role, role));
where role you can get the following, for example:
var userIdentity = UserManager.FindAsync(user, password).Result;
var role = RoleManager.FindById(userIdentity.Roles.First().RoleId).Name;
User and password you have to send a request.
I also want to give an example of code which will send request and receive response. To not have to look for and immediately start coding.
async Task<string> GetToken(string userName, string password)
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>( "user", userName ),
new KeyValuePair<string, string> ( "password", password )
}
);
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.PostAsync(APP_PATH + "/Authenticate", content);
var result = await response.Content.ReadAsStringAsync();
return result;
}
}
async Task<string> GetUserInfo(string token)
{
using (var client = CreateClient(token))
{
var response = await client.GetAsync(APP_PATH + "/ValidateToken");
return await response.Content.ReadAsStringAsync();
}
}
HttpClient CreateClient(string accessToken = "")
{
var client = new HttpClient();
if (!string.IsNullOrWhiteSpace(accessToken))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
return client;
}
All have only to call the appropriate methods in the correct order. I hope that is useful to someone.
P.S.
If You create a new project in Visual Studio to get this functionality you just need to tick:

Securing a Web API with Windows Server 2012 R2 ADFS 3.0 and Katana

I would like to make a MVC Web Application that talks to a Web API application and use ADFS 3.0 (on Windows 2012 R2) for authentication.
I managed to make the MVC Web Application to authenticate using ADFS.
and configured everything as shown in this article by Vittorio Bertocci
http://www.cloudidentity.com/blog/2013/10/25/securing-a-web-api-with-adfs-on-ws2012-r2-got-even-easier/
Now I use the latest pre release of AAL from nuget
Now after authenticating with ADFS from web MVC app, I try to call the webapi
public async Task<String> CallSecuredAPI()
{
string authority = "https://fs.domain.com/adfs";
string resourceURI = "https://{hostheader}/SecuredAPI";
string clientID = "ExternalWebSite1";
string clientReturnURI = "https://{hostheader}/ExternalSite";
AuthenticationContext ac = new AuthenticationContext(authority, false);
AuthenticationResult ar = ac.AcquireToken(resourceURI, clientID, new Uri(clientReturnURI));
string authHeader = ar.CreateAuthorizationHeader();
var client = new HttpClient();
HttpRequestMessage request =
new HttpRequestMessage(HttpMethod.Get, "https://hostheader/SecuredAPI/api/Claims");
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
but I get this error which I think is with the client not being a UI based client or WPF , windows App. Can someone let me know whether I am doing something wrong.
![Error when trying to get Authorization code using AAL][1]
Server Error in '/ExternalSite' Application.
Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.
Source Error:
Line 43:
Line 44: AuthenticationContext ac = new AuthenticationContext(authority, false);
Line 45: AuthenticationResult ar = ac.AcquireToken(resourceURI, clientID, new Uri(clientReturnURI));
Line 46:
Line 47: string authHeader = ar.CreateAuthorizationHeader();
Source File: c:\Users\balakrishna.takkalla\Documents\Visual Studio 2013\Projects\ExternalSite\ExternalSite\Controllers\HomeController.cs Line: 45
Stack Trace:
[InvalidOperationException: Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.]
System.Windows.Forms.Form.ShowDialog(IWin32Window owner) +5701502
Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.WindowsFormsWebAuthenticationDialog.ShowBrowser() +18
Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.WindowsFormsWebAuthenticationDialog.OnAuthenticate() +23
Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.WindowsFormsWebAuthenticationDialogBase.AuthenticateAAD(Uri requestUri, Uri callbackUri) +284
Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.InteractiveWebUI.OnAuthenticate() +103
Microsoft.IdentityModel.Clients.ActiveDirectory.OAuth2Request.SendAuthorizeRequest(Authenticator authenticator, String resource, Uri redirectUri, String clientId, String userId, PromptBehavior promptBehavior, String extraQueryParameters, IWebUI webUi, CallState callState) +363
Microsoft.IdentityModel.Clients.ActiveDirectory.<>c__DisplayClass9b.<AcquireAuthorization>b__9a() +111
System.Threading.Tasks.Task.Execute() +110
if I understood correctly: you want to access a Web API from the code-behind of an MVC application.
That topology is possible with Azure Active Directory today, you can see that in action in the sample https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet (I am in the process of updating it to the latest ADAL refresh, you can take a peek at the RCUpdate branch to see the work in progress).
However that topology is NOT achievable today from ADFS WS2012 R2. The reason is that an MVC app (and any other web site) is a confidential client, which OAuth2 handles differently from a public client (the WPF app you used as a starting point is a public client). In the scenario you are targeting, to use ADAL for getting a token from a confidential client you would use ADAL's method AcquireTokenByAuthorizationCode (see the sample I mentioned). However ADFS WS2012 R2 is incapable of processing that method. Today the OAuth2 support in ADFS WS2012 R2 is limited to public clients only.
Sorry for bringing bad news! As a mitigation, you might consider federating your ADFS with an AAD tenant: at that point you would be able to do what you want, authenticating as an ADFS user but getting tokens from AAD (which does support the necessary OAuth2 grant).
HTH
V.

Resources