I don't even know where to begin. I am a Windows Forms C# developer. I am trying to learn Azure. I am following a tutorial here: https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi
I have checked and rechecked my steps. I can successfully log in to the app. I can see the claims displayed when I click the claims button. As soon I click the To-Do List button, my app crashes in TaskController.cs Index method. It crashes on trying to obtain an AuthenticationResult.
// GET: Makes a call to the API and retrieves the list of tasks
public async Task<ActionResult> Index()
{
try
{
// Retrieve the token with the specified scopes
var scope = new string[] { Globals.ReadTasksScope };
//string[] scopes = new string[] { "user.read" };
var app = publicClientApplicationBuilder
.Create(Globals.ClientId)
.WithB2CAuthority(Globals.B2CAuthority)
.Build();
// IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
// var accounts = await cca.GetAccountsAsync();
//AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
//var accounts = await app.GetAccountsAsync(Globals.SignUpSignInPolicyId);
//var accounts = await app.GetAccountsAsync(Globals.SignUpSignInPolicyId);
//AuthenticationResult ar = await app.AcquireTokenInteractive(scope)
// .WithAccount(accounts.FirstOrDefault())
// .ExecuteAsync();
AuthenticationResult result;
try
{
IEnumerable<IAccount> accounts = await app.GetAccountsAsync(Globals.SignUpSignInPolicyId);
// Try to acquire an access token from the cache. If an interaction is required, MsalUiRequiredException will be thrown.
result = await app.AcquireTokenSilent(scope, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// Acquiring an access token interactively. MSAL will cache it so you can use AcquireTokenSilent on future calls.
result = await app.AcquireTokenInteractive(scope)
.ExecuteAsync();
}
//var accounts = await cca.GetAccountsAsync(Globals.SignUpSignInPolicyId);
// var accounts = await app.GetAccountsAsync();
//AuthenticationResult ar;
//try
//{
// ar = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
// .ExecuteAsync();
//}
//catch (MsalUiRequiredException)
//{
// ar = await app.AcquireTokenInteractive(scopes)
// .ExecuteAsync();
//}
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiEndpoint);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
// Handle the response
switch (response.StatusCode)
{
case HttpStatusCode.OK:
string responseString = await response.Content.ReadAsStringAsync();
JArray tasks = JArray.Parse(responseString);
ViewBag.Tasks = tasks;
return View();
case HttpStatusCode.Unauthorized:
return ErrorAction("Please sign in again. " + response.ReasonPhrase);
default:
return ErrorAction("Error. Status code = " + response.StatusCode + ": " + response.ReasonPhrase);
}
}
catch (MsalUiRequiredException ex)
{
/*
If the tokens have expired or become invalid for any reason, ask the user to sign in again.
Another cause of this exception is when you restart the app using InMemory cache.
It will get wiped out while the user will be authenticated still because of their cookies, requiring the TokenCache to be initialized again
through the sign in flow.
*/
return new RedirectResult("/Account/SignUpSignIn?redirectUrl=/Tasks");
}
catch (Exception ex)
{
return ErrorAction("Error reading to do list: " + ex.Message);
}
}
As you see, I have commented out several attempts to get this to work. At the end of the tutorial, it says:
Known Issues
MSAL cache needs a TenantId along with the user's ObjectId to function. It retrieves these two from the claims returned in the id_token. As TenantId is not guaranteed to be present in id_tokens issued by B2C unless the steps listed in this document, if you are following the workarounds listed in the doc and tenantId claim (tid) is available in the user's token, then please change the code in ClaimsPrincipalsExtension.cs GetB2CMsalAccountId() to let MSAL pick this from the claims instead.
I have noticed that in the app variable in my code, the TenantID property is null. I followed the work-arounds listed in the document, and still no TenantID.
Here are the steps listed in the document: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/AAD-B2C-specifics#acquiring-a-token-to-apply-a-policy
I followed all steps, not just from the bookmarked location and below.
Here is what I am seeing when I debug:
Here is what I am seeing from app:
Notice the UserTokenCache.NullPreferredUsernameDisplayLabel says:
Missing from the token response
It talks about that in the known issues mentioned above.
Now the accounts variable has the following:
The result variable stays null and execution goes to the catch block where it tries again to get a result using AcquireTokenInteractive(scope). This also fails and execution moves to the final catch block at the bottom of the method.
The error message states:
ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.
That doesn't make sense to me. I think the problem starts with the fact that the app variable is not acquiring the TenantID. But I do not know what to do about it.
I noticed a couple of things missing from the tutorial. It did not say to grant Admin privileges to the scope and API permissions, but I did that.
Here is my web.config from the TaskWebApp project:
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<add key="ida:Tenant" value="ShoppingCartB2C.onmicrosoft.com" />
<!--MSAL cache needs a tenantId along with the user's objectId to function. It retrieves these two from the claims returned in the id_token.
As tenantId is not guaranteed to be present in id_tokens issued by B2C unless the steps listed in this
document (https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/AAD-B2C-specifics#caching-with-b2c-in-msalnet).
If you are following the workarounds listed in the doc and tenantId claim (tid) is available in the user's token, then please change the
code in <ClaimsPrincipalsExtension.cs GetB2CMsalAccountId()> to let MSAL pick this from the claims instead -->
<add key="ida:TenantId" value="db1b052a-415c-4604-887c-e27b59860001" />
<add key="ida:ClientId" value="975f1457-e3e2-4cb8-b069-6b0b6b46611d" />
<add key="ida:ClientSecret" value="Gw4.3o-DRDr.j_828H-JMfsk_Jd1d-jQ5p" />
<add key="ida:AadInstance" value="https://ShoppingCartB2C.b2clogin.com/tfp/{0}/{1}" />
<add key="ida:RedirectUri" value="https://localhost:44316/" />
<add key="ida:SignUpSignInPolicyId" value="B2C_1_signupsignin1" />
<add key="ida:EditProfilePolicyId" value="B2C_1_edit_profile" />
<add key="ida:ResetPasswordPolicyId" value="B2C_1_reset" />
<add key="api:TaskServiceUrl" value="https://localhost:44332/" />
<!-- The following settings is used for requesting access tokens -->
<add key="api:ApiIdentifier" value="https://ShoppingCartB2C.onmicrosoft.com/demoapi/" />
<add key="api:ReadScope" value="read" />
<add key="api:WriteScope" value="write" />
</appSettings>
And my TaskService web.config:
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<add key="ida:AadInstance" value="https://ShoppingCartB2C.b2clogin.com/{0}/{1}/v2.0/.well-known/openid-configuration" />
<add key="ida:Tenant" value="ShoppingCartB2C.onmicrosoft.com" />
<add key="ida:ClientId" value="1c8e9aee-d04a-4fb1-aa32-8ba808122e76" />
<add key="ida:SignUpSignInPolicyId" value="B2C_1_signupsignin1" />
<!-- The following settings is used for requesting access tokens -->
<add key="api:ReadScope" value="read" />
<add key="api:WriteScope" value="write" />
</appSettings>
Please let me know how I can go about troubleshooting this. Out of everything I have tried, app always ends up with a null TenantID.
Have also tried this that ends up with null TenantID:
https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token?tabs=dotnet#acquire-a-token-interactively
See below:
string[] scopes = new string[] {"user.read"};
var app = PublicClientApplicationBuilder.Create(clientId).Build();
var accounts = await app.GetAccountsAsync();
AuthenticationResult result;
try
{
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch(MsalUiRequiredException)
{
result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
}
It seems to be mapping web app to AAD. Please check these steps once, hope it helps you:
Null Tenant Id will come during the mapping the web app to Azure active directory (B2B/B2C/AD).
Follow these steps present in these Microsoft documentations:
Integrate web app to Azure AD B2C
As you said, the application crashes and trying to obtain authenticationResult, make sure these settings configured correctly in order to get rid of that error.
Related
I'm using Microsoft Graph to retrieve all members of a Group. But I am getting the below authorization error message. I am not being able to figure it out as i started to learn it from today. I also went through some blogs regarding this but couldn't find out the root cause.
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: Microsoft.Graph.ServiceException: Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation.
Inner error:
AdditionalData:
date: 2021-09-24T11:25:11
request-id: ef3e82a6-f1df-4018-bccb-2eea075a934f
client-request-id: ef3e82a6-f1df-4018-bccb-2eea075a934f
ClientRequestId: ef3e82a6-f1df-4018-bccb-2eea075a934f
Following is my code:
private static string appId = ConfigurationManager.AppSettings["ida:AppId"];
private static string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
private static List<string> graphScopes =
new List<string>(ConfigurationManager.AppSettings["ida:AppScopes"].Split(' '));
public static async Task<IEnumerable<Event>> GetEventsAsync()
{
var graphClient = GetAuthenticatedClient();
var members = await graphClient.Groups["00000000-0000-0000-0000-000000000000"].Members.Request().GetAsync();
}
private static GraphServiceClient GetAuthenticatedClient()
{
return new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithClientSecret(appSecret)
.Build();
var tokenStore = new SessionTokenStore(idClient.UserTokenCache,
HttpContext.Current, ClaimsPrincipal.Current);
var accounts = await idClient.GetAccountsAsync();
var result = await idClient.AcquireTokenSilent(graphScopes, accounts.FirstOrDefault())
.ExecuteAsync();
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
}));
}
AppSettings:
<appSettings>
<add key="ida:AppID" value=[App Id] />
<add key="ida:AppSecret" value=[App Secret] />
<add key="ida:RedirectUri" value="https://localhost:44359/" />
<add key="ida:AppScopes" value="User.Read.All Calendars.Read" />
</appSettings>
I am assuming that I may need to adjust the AppScopes values in the appSettings but not sure. Can anyone provide me some hits to solve this?
Thanks in advance.
To get a list of group's members you need the following permissions
In your app settings you specified different set of permissions: User.Read.All Calendars.Read
Check permissions for your application in Azure Portal
Resource:
List members
I am trying to get a POST request working. I am using Windows Form and WebClient class to consume a REST Web API that I created. The windows form will transmit a list of object to the Rest Web API.
My WinApp Code
public static void BacklogListAdd(List<qmtRequest> _data)
{
var wi = System.Security.Principal.WindowsIdentity.GetCurrent();
var wic = wi.Impersonate();
var data = JsonConvert.SerializeObject(_data);
var urlLocal = "http://localhost:56499/request/item/add";
var url = "http://169.10.77.243/spa_solutions/ph18-mdm003-fe/request/item/add";
using (var client = new WebClient { UseDefaultCredentials = true })
{
client.Headers.Add(HttpRequestHeader.ContentType,
"application/json; charset=utf-8");
client.UploadData(
"http://169.10.77.243/spa_solutions/ph18-mdm003-fe/request/item/wee",
"POST",
Encoding.UTF8.GetBytes(data));
}
}
Web API Code
[Authorize]
[HttpPost]
[Route("Item/Wee")]
public IHttpActionResult BacklogAddItem(List<qmtRequest> _RequestList)
{
using (qmtdb)
{
qmtdb.qmtRequests.AddRange(_RequestList);
qmtdb.SaveChanges();
return Ok();
}
}
I have done some troubleshooting though, it is working if I try to use GET instead of POST. Another note, when I try using POST in localhost it works.
Fiddler
Web Config
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Methods" value="GET, POST" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Credentials" value="true" />
405 status code as Method Not Allowed Error .
Check you REST API method type and same method type call .
Hopefully you will get the response from your REST API
I'm trying to resume a conversation between a bot and a user from a Web Job, and I'm getting an Unauthorized Exception.
I can successfully reply to a conversation in my MessagesController class, but when I try to run the following code in my Web Job, I get the following exception:
private static async Task SendAlertToUser(ChannelAccount botAccount, LipSenseUser user, AlertFlags alert, string extra)
{
Console.WriteLine($"Sending alert to user: {user.Name}");
var sb = new StringBuilder(GetTextFromAlert(alert));
if (!string.IsNullOrEmpty(extra))
{
sb.AppendLine();
sb.Append(extra);
}
var userAccount = new ChannelAccount(user.ChannelId, user.Name);
var connector = new ConnectorClient(new Uri(user.ChannelUri));
var message = Activity.CreateMessageActivity();
message.From = botAccount;
message.Recipient = userAccount;
var conversation = await connector.Conversations.CreateDirectConversationAsync(botAccount, userAccount);
message.Conversation = new ConversationAccount(id: conversation.Id);
message.Locale = "en-Us";
message.Text = sb.ToString();
await connector.Conversations.SendToConversationAsync((Activity)message);
}
And the exception is:
Exception:System.UnauthorizedAccessException
Authorization for Microsoft App ID 58c04dd1-1234-5678-9123-456789012345 failed with status code Unauthorized and reason phrase 'Unauthorized'
When I inspect the connector's Credentials, I see that everything is correct. I've set a breakpoint in my MessagesController and inspected the connector's Credentials from there, and everything is identical.
Also, when I look at the IntelliTrace, I see the following messages:
My user.ChannelUri is "https://facebook.botframework.com", which I pulled off of the user when they initialized the conversation. Is that not the correct Uri?
Is there anything else I need to do to send a message? My App.config appSettings looks like this:
<appSettings>
<add key="BotId" value="MyBotName" />
<add key="MicrosoftAppId" value="58c04dd1-1234-5678-9123-456789012345" />
<add key="MicrosoftAppPassword" value="5xyadfasdfaweraeFAKEasdfad" />
<add key="AzureWebJobsStorage" value="DefaultEndpointsProtocol=https;AccountName=BLAH;AccountKey=THIS IS A KEY" />
</appSettings>
Answer from Bot Framework Team on a different channel:
You need to add a call to:
MicrosoftAppCredentials.TrustServiceUrl(serviceUrl);
This is done automatically when you are replying to a message, but for
proactive messages from another process you need to do this.
I need to maintain an OpenID login between two different browsers (i.e. Chrome and IE). How can this be done?
I am using this code:
https://ipp.developer.intuit.com/0010_Intuit_Partner_Platform/0200_DevKits_for_Intuit_Partner_Platform/0300_Windows_Azure_Program_for_Intuit_Anywhere/0001_Installing_the_Windows_Azure_SDK_2.0_for_Intuit_Anywhere
Here is my code:
/// <summary>
/// Action Result for Index, This flow will create OAuthConsumer Context using Consumer key and Consuler Secret key
/// obtained when Application is added at intuit workspace. It creates OAuth Session out of OAuthConsumer and Calls
/// Intuit Workpsace endpoint for OAuth.
/// </summary>
/// <returns>Redirect Result.</returns>
public RedirectResult Index()
{
oauth_callback_url = Request.Url.GetLeftPart(UriPartial.Authority) + ConfigurationManager.AppSettings["oauth_callback_url"];
consumerKey = ConfigurationManager.AppSettings["consumerKey"];
consumerSecret = ConfigurationManager.AppSettings["consumerSecret"];
oauthLink = Constants.OauthEndPoints.IdFedOAuthBaseUrl;
IToken token = (IToken)Session["requestToken"];
IOAuthSession session = CreateSession();
IToken requestToken = session.GetRequestToken();
Session["requestToken"] = requestToken;
RequestToken = requestToken.Token;
TokenSecret = requestToken.TokenSecret;
oauthLink = Constants.OauthEndPoints.AuthorizeUrl + "?oauth_token=" + RequestToken + "&oauth_callback=" + UriUtility.UrlEncode(oauth_callback_url);
return Redirect(oauthLink);
}
<add key="webpages:Version" value="1.0.0.0"/>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
<add key="oauth_callback_url" value="/OauthResponse"/>
<add key="menuProxy" value="MenuProxy"/>
<add key="grantUrl" value="OauthGrant"/>
<add key="qbo_base_url" value="https://qbo.intuit.com/qbo1/rest/user/v2/"/>
<!-- Enter the Application Name by replacing YourAppName -->
<add key="openid_identifier" value="https://openid.intuit.com/Identity-YourAppName"/>
You would need to map the openid with the oauth tokens obtained for that user and persist this in a store. The next time the user logs in you should build logic within your application to lookup the tokens for that user (based on openid).
SOLUTION
My working solution can be found in the answer or in my update two.
1) Now make sure, for testing on localhost, that you have setup windows firewalls for inbound on the localhost port. Port forwarding on the router if you have one.
2) Then you need to tell IIS Express that its okay that the request comes from outsite the localhost:
Find Documents\IISExpress\config and edit applicationhost.config. Find your site in the list and remove the localhost from the binding.
<site name="S-Innovations.TrafficTheory.Web2" id="1591449597">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="G:\Documents\Visual Studio 2012\Projects\S-Innovations.TrafficTheory\S-Innovations.TrafficTheory.Web2" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:909090:localhost" />
</bindings>
</site>
2a) ISS need to run as administrator, running visual studio as administrator also starts iss as admin...
3) Locate your ip, www.myip.com and change the ACS return uri to : http://90.90.90.90:909090/api/federation/
4) change the webbroker to use your ip also:
WebAuthenticationResult webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
new Uri("https://traffictheory.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=http%3a%2f%2flocalhost%3a48451%2f"),
new Uri("http://99.99.99.99:909090/api/federation/end"));
Everything worked for me like this. I got a hello world passed on to my metro app as the token.
Problem
I have set up a WCF Service and a Metro App.
The WCF service is set up to authenticate using Azure ACS.
I made a Console Application that works with the WebService and ACS:
static void Main(string[] args)
{
try
{
// First start the web project, then the client
WebClient client = new WebClient();
var token = RetrieveACSToken();
client.Headers.Add("Authorization", token);
client.Headers.Add("Content-type", "text/xml");
var url = new Uri("http://traffictheory.azurewebsites.net/UserService.svc/Users");
//var url = new Uri("http://localhost:4000/UserService.svc/Users");//
Stream stream = client.OpenRead(url);
StreamReader reader = new StreamReader(stream);
String response = reader.ReadToEnd();
Console.Write(response);
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
Console.ReadLine();
}
private static string RetrieveACSToken()
{
var acsHostName = ConfigurationManager.AppSettings.Get("ACSHostName");
var acsNamespace = ConfigurationManager.AppSettings.Get("ACSNamespace");
var username = ConfigurationManager.AppSettings.Get("ServiceIdentityUserName");
var password = ConfigurationManager.AppSettings.Get("ServiceIdentityCredentialPassword");
var scope = "http://traffictheory.azurewebsites.net/";
//var scope = "http://localhost:4000/";//
// request a token from ACS
WebClient client = new WebClient();
client.BaseAddress = string.Format("https://{0}.{1}", acsNamespace, acsHostName);
NameValueCollection values = new
NameValueCollection();
values.Add("wrap_name", username);
values.Add("wrap_password", password);
values.Add("wrap_scope", scope);
byte[] responseBytes =
client.UploadValues("WRAPv0.9", "POST", values);
string response =
Encoding.UTF8.GetString(responseBytes);
string token = response
.Split('&')
.Single(value =>
value.StartsWith("wrap_access_token=",
StringComparison.OrdinalIgnoreCase))
.Split('=')[1];
var decodedToken = string.Format("WRAP access_token=\"{0}\"", HttpUtility.UrlDecode(token));
return decodedToken;
}
I face two problems now when i want to use it from my Metro App.
First one is unrelated to the service and is about the WebAuthenticationBroker.
1)
When i use
WebAuthenticationResult webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
new Uri("https://s-innovations.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=http%3a%2f%2ftraffictheory.azurewebsites.net%2f"),
new Uri("https://s-innovations.accesscontrol.windows.net")
);
I am able to log in using, LiveID, Facebook ect. Not google because ACS dont include the ID correctly. But I dont get any kind of token back or Claims. I only get:
https://s-innovations.accesscontrol.windows.net/v2/wsfederation?wa=wsignin1.0
https://s-innovations.accesscontrol.windows.net/v2/facebook?cx=cHI9d3NmZWRlcmF0aW9uJn...cmFmZmljdGhlb3J5LmF6dXJld2Vic2l0ZXMubmV0JTJmJmlwPUZhY2Vib29rLTM1NTk5MjQ2NzgxNzc5OQ2&code=AQDagvqoXQ.......
How do I get the claims like in the end of this movie:
http://channel9.msdn.com/Events/BUILD/BUILD2011/SAC-858T
His app works!
2)
The console app shown above get authenticated and get the token to pass to the service when calling the API, how do i get this token from within the metro app.
UPDATE
I created the controller as suggested:
[HttpPost]
public ActionResult End()
{
return Json("Hello World");
}
I have put in a break point to see if it get it. No hit yet.
WebAuthenticationResult webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
new Uri("https://traffictheory.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=http%3a%2f%2flocalhost%3a48451%2f"),
new Uri("http://localhost:909090/Federation/End"));
On my Relying Party Application i ahave
Realm http://localhost:909090/
Return Url: Nothing (have tried http://localhost:909090/Federation/End )
The response data contains : http://localhost:909090/Federation/End right now.
UPDATE 2
I also tried with an api controller as you shown in another post:
public class FederationController : ApiController
{
public HttpResponseMessage Post()
{
var response = this.Request.CreateResponse(HttpStatusCode.Redirect);
response.Headers.Add("Location", "/api/federation/end?acsToken=" + ExtractBootstrapToken());
return response;
}
public string Get()
{
return "hello world";
}
protected virtual string ExtractBootstrapToken()
{
return "Hello World";
}
}
Now the login screen just hang and ends with a service you looking for is not ready right now (or something like that).
acs return url http://localhost:48451/api/Federation
WebAuthenticationResult webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
new Uri("https://traffictheory.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=http%3a%2f%2flocalhost%3a909090%2f"),
new Uri("http://localhost:909090/api/federation/end"));
The WebAuthenticationBroker simply keeps browsing until the next requested page is the one specified by the callbackUri parameter. At that point it returns the final URL to you so if you want to get anything back it needs to be encoded in that URL.
In the ACS control panel for the relying party you need to specify a return url that is somewhere on your site. For example https://traffictheory.azurewebsites.net/federationcallback. Then create a controller to handle accept a post to that URL. The post will have a form field wresult which is some xml that will contain the token returned from ACS.
You can then send the token back to the WebAuthenticationBroker by redirecting to https://traffictheory.azurewebsites.net/federationcallback/end?token={whatever you want to return}
You would then need to change the usage of the authentication broker to the following:
var webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
new Uri("https://s-innovations.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=http%3a%2f%2ftraffictheory.azurewebsites.net%2f"),
new Uri("https://traffictheory.azurewebsites.net/federationcallback/end")
);
// The data you returned
var token = authenticateResult.ResponseData.Substring(authenticateResult.ResponseData.IndexOf("token=", StringComparison.Ordinal) + 6);
My controller for handling the authentication callback post looks like this.
public class FederationcallbackController : ApiController
{
public HttpResponseMessage Post()
{
var response = this.Request.CreateResponse(HttpStatusCode.Redirect);
response.Headers.Add("Location", "/api/federationcallback/end?acsToken=" + ExtractBootstrapToken());
return response;
}
protected virtual string ExtractBootstrapToken()
{
return HttpContext.Current.User.BootstrapToken();
}
}
The BootstrapToken() extenion method is part of the wif.swt NuGet package. By default WIF doesn't save anything to the bootstrap token property you need to enable it by including the saveBootstrapTokens="true" attribute on the <service> element under <microsoft.identityModel> in your web.config. Mine looks like this:
<microsoft.identityModel>
<service saveBootstrapTokens="true">
<audienceUris>
<add value="http://localhost:3949/" />
</audienceUris>
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true" issuer="https://xyz.accesscontrol.windows.net/v2/wsfederation" realm="http://localhost:3949/" reply="http://localhost:3949/" requireHttps="false" />
<cookieHandler requireSsl="false" path="/" />
</federatedAuthentication>
<issuerNameRegistry type="Microsoft.IdentityModel.Swt.SwtIssuerNameRegistry, Wif.Swt">
<trustedIssuers>
<add name="https://readify.accesscontrol.windows.net/" thumbprint="{thumbprint}" />
</trustedIssuers>
</issuerNameRegistry>
<securityTokenHandlers>
<add type="Microsoft.IdentityModel.Swt.SwtSecurityTokenHandler, Wif.Swt" />
</securityTokenHandlers>
<issuerTokenResolver type="Microsoft.IdentityModel.Swt.SwtIssuerTokenResolver, Wif.Swt" />
</service>
</microsoft.identityModel>