I'm literally going crazy trying to fix this but nothing I do seems to make a difference. When I navigate to localhost:3978/api/callback it throws
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost:3978/api/callback'.
</Message>
<MessageDetail>
No action was found on the controller 'Callback' that matches the request.
</MessageDetail>
</Error>
This is the controller in my Controllers/CallbackController.cs
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Configuration;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using Autofac;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace VSTF_RD_Bot.Controllers
{
public class CallbackController : ApiController
{
[HttpGet]
[Route("api/Callback")]
public async Task<HttpResponseMessage> Callback([FromUri] string state, [FromUri] string code)
{
//parse out the userId and convoId from states parameter
string[] states = state.Split(new[] { "," }, StringSplitOptions.None);
string userId = states[0];
string conversationId = states[1];
// Check if the bot is running against emulator
var connectorType = HttpContext.Current.Request.IsLocal ? ConnectorType.Emulator : ConnectorType.Cloud;
// Exchange the Facebook Auth code with Access toekn
var token = await AdHelpers.ExchangeCodeForAccessToken(userId, conversationId, code, "redirect_uri");
// Create the message that is send to conversation to resume the login flow
var msg = new Message
{
Text = $"token:{token}",
From = new ChannelAccount { Id = userId },
To = new ChannelAccount { Id = Constants.botId },
ConversationId = conversationId
};
var reply = await Conversation.ResumeAsync(Constants.botId, userId, conversationId, msg, connectorType: connectorType);
// Remove the pending message because login flow is complete
IBotData dataBag = new JObjectBotData(reply);
PendingMessage pending;
if (dataBag.PerUserInConversationData.TryGetValue("pendingMessage", out pending))
{
dataBag.PerUserInConversationData.RemoveValue("pendingMessage");
var pendingMessage = pending.GetMessage();
reply.To = pendingMessage.From;
reply.From = pendingMessage.To;
// Send the login success asynchronously to user
var client = Conversation.ResumeContainer.Resolve<IConnectorClient>(TypedParameter.From(connectorType));
await client.Messages.SendMessageAsync(reply);
return Request.CreateResponse("You are now logged in! Continue talking to the bot.");
}
else
{
// Callback is called with no pending message as a result the login flow cannot be resumed.
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, new InvalidOperationException("Cannot resume!"));
}
}
}
}
What am I missing here?
This is my webApiconfig.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace VSTF_RD_Bot
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Json settings
config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented;
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
};
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Make your controller
[HttpGet]
[Route("api/Callback/{state}/{code}")]
public async Task<HttpResponseMessage> Callback(string state, string code)
{
and request url
"localhost:3978/api/Callback/samplestate/samplecode"
Related
Hi guys I am trying to add oAuth 2.0 oidc to .net Webforms app running .net 48 and am getting this error
'IDX21307: The 'c_hash' claim was not found in the id_token, but a 'code' was in the OpenIdConnectMessage'
if I don't use MessageReceived notification then it goes to 404 resource not found after going through AuthorizationCodeReceived I used MessageRecieved notification after going through comments on this answer
not sure what to do or how to debug here is the startup.cs
using IdentityModel.Client;
using Microsoft.AspNet.Identity;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
[assembly: OwinStartup(typeof(http_SpectraWeb.Startup))]
namespace http_SpectraWeb
{
public class Startup
{
// These values are stored in Web.config. Make sure you update them!
private readonly string _clientId = ConfigurationManager.AppSettings["okta:ClientId"];
private readonly string _redirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"];
private readonly string _authority = ConfigurationManager.AppSettings["okta:OrgUri"];
private readonly string _clientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"];
private readonly string _groupPrefix = ConfigurationManager.AppSettings["okta:GroupPrefix"];
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
List<Claim> claims = new List<Claim>();
private async Task ProcessMessageReceivedNotification(MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> args)
{
if (!string.IsNullOrWhiteSpace(args.ProtocolMessage.Code))
{
// Exchange code for access and ID tokens
TokenClient tokenClient = new TokenClient(new HttpClient() { BaseAddress = new Uri($"{_authority}/v1/token") }, new TokenClientOptions { ClientId = _clientId, ClientSecret = _clientSecret });
var tokenResponse = await tokenClient.RequestAuthorizationCodeTokenAsync(args.ProtocolMessage.Code, _redirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var client = new HttpClient();
var userInfoResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = $"{_authority}/v1/userinfo",
Token = tokenResponse.AccessToken
});
args.ProtocolMessage.IdToken = tokenResponse.IdentityToken;
claims.AddRange(userInfoResponse.Claims);
claims.AddRange(userInfoResponse.Claims);
claims.Add(new Claim("id_token", tokenResponse.IdentityToken));
claims.Add(new Claim("access_token", tokenResponse.AccessToken));
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
{
claims.Add(new Claim("refresh_token", tokenResponse.RefreshToken));
}
}
}
public void ConfigureAuth(IAppBuilder app)
{
IdentityModelEventSource.ShowPII = true;
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = _clientId,
ClientSecret = _clientSecret,
Authority = _authority,
RedirectUri = _redirectUri,
ResponseType = OpenIdConnectResponseType.Code,
Scope = OpenIdConnectScope.OpenIdProfile,
UsePkce = false,
TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name" },
Notifications = new OpenIdConnectAuthenticationNotifications
{
MessageReceived = ProcessMessageReceivedNotification,
RedirectToIdentityProvider = n =>
{
return Task.FromResult(0);
},
AuthorizationCodeReceived = async n =>
{
if (n.AuthenticationTicket != null)
{
n.AuthenticationTicket.Identity.AddClaims(claims);
}
},
},
});
}
}
}
any help would be great
Thanks
I have created an HTTP trigger azure function, which holds the code (below) to upload a video to YouTube automatically. Source: (https://developers.google.com/youtube/v3/docs/videos/insert).
When I try to run the app locally using visual studio, I am getting the following error:
Executed 'Function1' (Failed, Id=d601d64a-2f2c-4f8a-8053-a2f33ca21dbc)
System.Private.CoreLib: Exception while executing function: Function1.
Google.Apis.Auth: At least one client secrets (Installed or Web)
should be set
It looks like a Google Authentication error, but I am unsure as to how to get this fixed and I see that YouTube API does not support Service account? How can this issue be fixed, is there a get around to this? Thanks in advance.
C# Code:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Upload;
using Google.Apis.YouTube.v3.Data;
using System.Reflection;
using Google.Apis.YouTube.v3;
using Google.Apis.Services;
using System.Threading;
namespace UploadVideo
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
log.LogInformation("YouTube Data API: Upload Video");
log.LogInformation("==============================");
try
{
await Run();
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
log.LogInformation("Error: " + e.Message);
}
}
return new OkObjectResult($"Video Processed..");
}
private static async Task Run()
{
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] { YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var video = new Video();
video.Snippet = new VideoSnippet();
video.Snippet.Title = "Default Video Title";
video.Snippet.Description = "Default Video Description";
video.Snippet.Tags = new string[] { "tag1", "tag2" };
video.Snippet.CategoryId = "22"; // See https://developers.google.com/youtube/v3/docs/videoCategories/list
video.Status = new VideoStatus();
video.Status.PrivacyStatus = "unlisted"; // or "private" or "public"
var filePath = #"C:\Users\Peter\Desktop\audio\test.mp4"; // Replace with path to actual movie file.
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*");
videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged;
videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived;
await videosInsertRequest.UploadAsync();
}
}
private static void videosInsertRequest_ProgressChanged(Google.Apis.Upload.IUploadProgress progress)
{
switch (progress.Status)
{
case UploadStatus.Uploading:
Console.WriteLine("{0} bytes sent.", progress.BytesSent);
break;
case UploadStatus.Failed:
Console.WriteLine("An error prevented the upload from completing.\n{0}", progress.Exception);
break;
}
}
private static void videosInsertRequest_ResponseReceived(Video video)
{
Console.WriteLine("Video id '{0}' was successfully uploaded.", video.Id);
}
}
}
It looks like you were trying to use the service account to do the OAuth2 web server flow, which wont work. The correct code form creating a service account credential is as follows.
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(scopes);
}
Note
as i have mentioned in your other questions the YouTube API does NOT support service account authentication. You must use Oauth2 and i am not convinced this can be done inside of azure functions. As there is no way to spawn the web browser window to request authorization of the user.
I am trying to make a web API Post method call as follows but it not working as expected,xmlcontent seems OK but somehow the formatting seems messed up when the request is being sent and the response throws an error ,I double checked the XML from python and it works,is there a better way to create and send the XML?what am I doing wrong?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
namespace WebApiXML
{
public class Program
{
static void Main(string[] args)
{
testWCF2(); //Or whatever
Console.ReadLine();
}
public static async Task testWCF2()
{
string xmlcontent = #"<SoftwareProductBuild>
<BuildSource>DATASOURCE</BuildSource>
<BuiltBy>username1</BuiltBy>
<CreatedBy>username1</CreatedBy>
<Name>username1_1959965_1969310_524f7fef-5b37-11e7-b4ee-f0921c133f10_UL.AB.1.2_test2</Name>
<Status>Approved</Status>
<BuiltOn>2017-06-27T06:20:30.275690</BuiltOn>
<Tag>username1_1959965_1969310_524f7fef-5b37-11e7-b4ee-f0921c133f10_test2</Tag>
<Keywords>
<KeywordInfo>
<Name>subystem</Name>
</KeywordInfo>
</Keywords>
<SoftwareImageBuilds>
<SoftwareImageBuild>
<Type>LA</Type>
<Name>username1_1959965_1969310_524f7fef-5b37-11e7-b4ee-f0921c133f10_UL.AB.1.2_test2</Name>
<Location>\\location1\data1\PRECOMMIT_OS_DEF</Location>
<Variant>PRECOMMIT_OS_DEF</Variant>
<LoadType>Direct</LoadType>
<Target>msm8998</Target>
<SoftwareImages>
<SoftwareImage>
<Name>UL.AB.1.2</Name>
</SoftwareImage>
</SoftwareImages>
</SoftwareImageBuild>
</SoftwareImageBuilds>
</SoftwareProductBuild>";
#region using
using (var client = new System.Net.Http.HttpClient())
{
var response = await client.PostAsXmlAsync("http://server:8100/api/SoftwareProductBuild", xmlcontent);
if (!response.IsSuccessStatusCode)
{
//throw new InvalidUriException("Some error with details.");
Console.WriteLine(response);
}
Console.WriteLine("Printing DEV Pool Response\n");
}
#endregion
//return null;
}
}
}
PostAsXmlAsync will try to serialize the object passed to it. So you have a string that contains XML and then try to post the string as XML(Double serialization).
Use StringContent, giving it the XML string value and set the content type to appropriate media type, then post it. i.e. client.PostAsync(url, content)
using (var client = new System.Net.Http.HttpClient()) {
var url = "http://server:8100/api/SoftwareProductBuild";
var content = new StringContent(xmlcontent, Encoding.UTF8, "application/xml");
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode) {
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine("Printing DEV Pool Response\n");
Console.WriteLine(responseBody);
} else {
Console.WriteLine(string.Format("Bad Response {0} \n", response.StatusCode.ToString()));
}
}
I am trying to use the new User Id provider specified in signalr 2 to send messages to a specific user. When I call the Clients.All method, I see this working as my javascript code gets called from the server and the ui produces some expected text for my test case. However, when I switch to Clients.User the client side code is never called from the server. I followed the code outlined in this example: SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*.
NotificationHub.cs:
public class NotificationHub : Hub
{
[Authorize]
public void NotifyUser(string userId, int message)
{
Clients.User(userId).DispatchMessage(message);
}
public override Task OnConnected()
{
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
}
IUserIdProvider.cs:
public class UserIdProvider : IUserIdProvider
{
MemberService _memberService;
public UserIdProvider()
{
}
public string GetUserId(IRequest request)
{
long UserId = 0;
if (request.User != null && request.User.Identity != null &&
request.User.Identity.Name != null)
{
var currenUser = Task.Run(() => _memberService.FindByUserName(request.User.Identity.Name)).Result;
UserId = currenUser.UserId;
}
return UserId.ToString();
}
}
Startup.cs
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Routes.MapHttpRoute(
"Default2",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
"DefaultApi2",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var idProvider = new UserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerAuthenticationProvider()
});
var hubConfiguration = new HubConfiguration
{
};
map.RunSignalR(hubConfiguration);
});
app.MapSignalR();
QuerstringOAuthBearerAuthenticationProvider:
public class QueryStringOAuthBearerAuthenticationProvider
: OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context == null) throw new ArgumentNullException("context");
// try to find bearer token in a cookie
// (by default OAuthBearerAuthenticationHandler
// only checks Authorization header)
var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];
if (!string.IsNullOrEmpty(tokenCookie))
context.Token = tokenCookie;
return Task.FromResult<object>(null);
}
}
Do I need to map the user to the connections myself using the IUserIdProvider through the OnConnected, OnDisconnected, etc. or does this happen automatically behind the scenes? Is there someone wrong in my posted code that could be a problem as well? I am running signalr from the same environment as my web api rest services, don't know if this makes a difference and using the default bearer token setup web api is using.
It would be far easier for you to create a group based on the connectionid of the connecting client, in the onConnected event and broadcast to the group that matches the connected id, that way if the client disconnects, when they reconnect they would simply belong to a new group the themselves. Unless of course you are required to have an authenticated user.
I'm building a small website in asp.net which let the user login via facebook and then the app
can post to user's timeline, i'm using scope="publish_actions,manage_pages" but when user login, app never asks the permission to post on the wall, it only gets the permission to access public profile, here is my code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using System.IO;
using Facebook;
namespace EadProject.Controllers
{
public class LoginController : Controller
{
//
// GET: /Login/
public ActionResult Index()
{
return View();
}
public ActionResult FacebookLogin()
{
string app_id = "1415837275360274";
string app_secret = "911b08ff5ae3cdaf74e512ce1c312c65";
string scope = "publish_actions,manage_pages";
if (Request["code"] == null)
{
Response.Redirect(string.Format("https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri={1}&scope={2}",app_id,Request.Url.AbsoluteUri,scope));
}
else
{
Dictionary<string, string> tokens = new Dictionary<string, string>();
string url = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&scope={2}&code={3}&client_secret={4}", app_id, Request.Url.AbsoluteUri, scope,Request["code"].ToString(),app_secret);
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
StreamReader reader = new StreamReader(response.GetResponseStream());
string vals = reader.ReadToEnd();
foreach (string token in vals.Split('&'))
{
tokens.Add(token.Substring(0,token.IndexOf("=")),token.Substring(token.IndexOf("=")+1,token.Length-token.IndexOf("=")-1));
}
}
string access_token = tokens["access_token"];
var client = new FacebookClient(access_token);
client.Post("/me/feed", new { message = "I am using QUiz Contest Pucit App developed by AHmed ALI :)" });
StreamWriter sw = new StreamWriter(new FileStream("C:\tokens.txt",FileMode.OpenOrCreate));
sw.WriteLine(access_token);
}
return View();
}
}
}
Please explain what is the problem?