Precondition Failed Error while trying to send Message to Teams-Chat | Graph API | C# - c#

everytime I try to send a Teams-Message I recieve an error.
I couldn`t figure out how to fix it and hope someone here has a clue for me.
Error Message:
Microsoft.Graph.ServiceException: "Code: PreconditionFailed
Message: Requested API is not supported in application-only context
Inner error:
AdditionalData:
date: 2022-10-20T12:07:44
request-id: 88e01bd9-370c-4739-b0bd-0244892475e2
client-request-id: 88e01bd9-370c-4739-b0bd-0244892475e2
ClientRequestId: 88e01bd9-370c-4739-b0bd-0244892475e2
"
Permissions:
Delegated Permissions:
Chat.ReadBasic, Chat.Read, Chat.ReadWrite, Chat.Create, ChatMember.Read, ChatMember.ReadWrite, ChatMessage.Send, Chat.ReadWrite, Channel.Delete.All, Group.ReadWrite.All, Directory.ReadWrite.All
Application Permissions:
ChatMember.Read.All, ChatMember.ReadWrite.All, Chat.ReadBasic.All, Chat.Read.All, Chat.Create, Chat.ReadWrite.All, Channel.Delete.Group, Channel.Delete.All, Group.ReadWrite.All, Directory.ReadWrite.All
Code I am using:
/* ------------------------------------------------------------- */
/// <summary>
///
/// </summary>
/// <param name="userId"></param>
/// <param name="chatID"></param>
/// <param name="messageText"></param>
/// <param name="scopes"></param>
/// <returns></returns>
public ChatMessage SendMessageToChat(string userId, string chatID, string messageText, string[] scopes = null)
{
return SendMessageToChatAsync(userId, chatID, messageText, scopes).GetAwaiter().GetResult();
}
/* ------------------------------------------------------------- */
private async Task<ChatMessage> SendMessageToChatAsync(string userId, string chatID, string messageText, string[] scopes = null)
{
GraphServiceClient graphClient = this.GetAuthenticatedGraphClient(scopes);
var chatMessage = new ChatMessage
{
Body = new ItemBody
{
Content = messageText
}
};
return await graphClient.Users[userId].Chats[chatID].Messages
.Request()
.AddAsync(chatMessage);
}
}
}

what you write is await graphClient.Users[userId].Chats[chatID].Messages, so it's obvious that you want to send chat message to a teams chat but not a teams channel.
Let's see the permissions for this graph api. It does not support application permissions. Combining with your error message, I deduce you used client credential flow and set the scope as https://graph.microsoft.com/.default. So you need to use delegate api permission. That means you need to have a module to let users sign in with their user#xxx.onmicrosoft.com account then calling graph api. If what you had is a web application, you need to integrate AAD like this sample demonstrating. If you owned a daemon app, then your requirement can't be realized.

Related

IdentityServer4: IDX20803: Unable to obtain configuration from 'https://<ids_server_url>/.well-known/openid-configuration'

Using:
Frontend: Angular 14,
API: .NET Core 5, c#, MVC
IDS: .NET Core 5, c#, Razor as per ID standard
For my web app I have an instance of IdentityServer 4 running. This worked perfectly fine and without hick ups for about a year. Since recently when the app starts the login still works flawlessly and provides the token as per usual.
However, any API request thereafter return a 500 error, for about 1 minute or so, after which it works fine and without issue. Until the app is in 'rest' position (i.e. no active users) it starts of with the same error for the same amount of time.
I tried installing serilog to see if I can catch the error on the API side, to no avail.
There are no errors in the logged serilog file.
The only errors I can find are in the ASP.NET logs, which generally llok like the below;
fail: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
Exception occurred while processing message.
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://<ids_server_url>/.well-known/openid-configuration'.
---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'https://<ids_server_url>/.well-known/openid-configuration'.
---> System.Net.Http.HttpRequestException: Only one usage of each socket address (protocol/network address/port) is normally permitted. (<ids_server_url>:443)
---> System.Net.Sockets.SocketException (10048): Only one usage of each socket address (protocol/network address/port) is normally permitted.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
Nor can I catch the error on the IDS side, as that also seems to be working fine.
Accessing the .well-known/openid-configuration directly (i.e. from browser) gives a direct and correct response.
Several posts on SO indicated to add the below;
IdentityModelEventSource.ShowPII = true;
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls12;
// | SecurityProtocolType.Tls13;
This didn't seem to do anything at all to improve the error.
Would anybody be able to point me in the directions of any other possibilities?
Especially the fact that it is only about a minute at the startup of the app seems to be weird?
I thought it might be the startup of IDS instance, but given that the actual login window repsonds directly and without delay, it implies that the IDS instance is active and running?
Any ideas would be helpfull?
update: 19/02/2023
With the help of #Tore Nestenius I have been able to add some logging to the initial process but the behaviour remains erratic and only on the deployed instance. (Likely because of app_pool shutting down)
Last night according to logger, after 6 failed attempts there was a succesfull query of the openid-configuration
JwtBearerBackChannelListener
#### SendASync: https://<ids_server_url>/.well-known/openid-configuration
#### success: True
#### completed: True
#### loadtime: 132
#### url: https://<ids_server_url>/.well-known/openid-configuration
But...
The subsequent process fails (again)
fail: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
What's more is that the initial call that the frontend makes is to a non-authorized endpoint (i.e. a public endpoint) there should not be a need for any token verification on that call?
If I query the backend on that endpoint directly from the browser it responds immediately, hence the backend appears to be working correctly? (i.e. api & database respond as expected when queried from the browser) yet in the API ASP logs it indicates a failed jwtHandler call? Weird...
Could it be a timing issue that, when you deploy your application that the client starts to request the discovery document before IdentityServer is up and running?
In AddOpenIDConnect and JwtBearer, you can define your own BackchannelHttpHandler, like this:
.AddJwtBearer(opt =>
{
opt.BackchannelHttpHandler = new JwtBearerBackChannelListener();
opt.BackchannelTimeout = TimeSpan.FromSeconds(60); //default 60s
...
}
This handler is used when it needs to load and reload the discovery document.
A sample handler can look like this:
public class JwtBearerBackChannelListener : DelegatingHandler
{
public JwtBearerBackChannelListener() : base(new HttpClientHandler())
{
Console.WriteLine();
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("JwtBearerBackChannelListener");
Console.WriteLine("#### SendASync: " + request.RequestUri);
var sw = new Stopwatch();
sw.Start();
var result = base.SendAsync(request, cancellationToken);
result.ContinueWith(t =>
{
sw.Stop();
Console.WriteLine("#### success: " + result.IsFaulted);
Console.WriteLine("#### loadtime: " + sw.ElapsedMilliseconds.ToString());
Console.WriteLine("#### url: " + request.RequestUri);
Serilog.Log.Logger.ForContext("SourceContext", "JwtBearerBackChannelListener")
.ForContext("url", request.RequestUri)
.ForContext("loadtime", sw.ElapsedMilliseconds.ToString() + " ms")
.ForContext("success", result.IsCompletedSuccessfully)
.Information("Loading IdentityServer configuration");
});
return result;
}
}
This allows you to add more extensive logging and also even custom retry logic.
It is important that IdentityServer is up-and-running before the client/api starts.
One approach to solve this is to add a middleware that blocks incoming requests from being processed until IdentityServer is online, like this:
Sample code for a waiting middleware
namespace PaymentAPI.Middleware
{
/// <summary>
/// Extension method to register the middleware
/// </summary>
public static class WaitForIdentityServerMiddlewareExtensions
{
public static IApplicationBuilder UseWaitForIdentityServer(this IApplicationBuilder builder, WaitForIdentityServerOptions options)
{
return builder.UseMiddleware<WaitForIdentityServerMiddleware>(options);
}
}
public class WaitForIdentityServerOptions
{
public string Authority { get; set; }
}
/// <summary>
/// ASP.NET Core middleware that will wait for IdentityServer to respond
///
/// It will return a 503 SERVICE UNAVAILABLE if IdentityServer is not responding
///
/// This middleware is only in use until the first successfull response from IdentityServer.
/// After that this module will not do anything.
///
/// It will add the following response headers to the resonse when we return a 503 error:
///
/// - x-reason: Waiting for IdentityServer
/// - Cache-Control: no-store,no-cache,max-age=0
/// - Retry-After: 5
///
/// The authority URL will be taken from the
///
/// Written by Tore Nestenius to be used in the IdentityServer in production training class.
/// https://www.tn-data.se
///
/// </summary>
public class WaitForIdentityServerMiddleware
{
/// <summary>
/// number of seconds between each attempt to contact IdentityServer
/// </summary>
private int secondsBetweenRetries = 2;
/// <summary>
/// How many seconds should we wait before we give up waiting?
/// </summary>
private int httpRequestTimeout = 3;
/// <summary>
/// True when we have been able to reach IdentityServer
/// </summary>
private bool _identityServerReady = false;
private readonly RequestDelegate _next;
private readonly string _discoveryUrl;
private readonly SemaphoreSlim _refreshLock;
private DateTimeOffset _syncAfter = DateTimeOffset.MinValue;
private readonly DateTime _startTime;
public WaitForIdentityServerMiddleware(RequestDelegate next, IConfiguration configuration, WaitForIdentityServerOptions options)
{
_next = next;
_startTime = DateTime.UtcNow;
_discoveryUrl = buildDiscoveryUrl(options.Authority);
_refreshLock = new SemaphoreSlim(1);
}
public async Task InvokeAsync(HttpContext context)
{
//Has IdentityServer has succesfully responsed yet?
if (_identityServerReady == false)
{
//Fail fast if we should wait a bit or if there is already a request is in progress
if (_syncAfter > DateTimeOffset.UtcNow ||
_refreshLock.CurrentCount == 0)
{
//We are waiting to not overload IdentitytServer with to many requests
//Just terminate the request with a 503 Service Unavailable response
CreateServiceUnavailableResponse(context);
return;
}
//Make sure we only do one request at the time
await _refreshLock.WaitAsync().ConfigureAwait(false);
try
{
//Still not answering?
if (_identityServerReady == false)
{
_identityServerReady = await TryToReachIdentityServer(context);
}
}
catch (Exception exc)
{
Log.Logger.ForContext("SourceContext", "WaitForIdentityServerMiddleware")
.ForContext("DiscoveryUrl", _discoveryUrl)
.ForContext("Exception", exc.Message)
.ForContext("Path", context.Request.Path)
.Fatal("Exception while trying to reach IdentityServer");
}
finally
{
_refreshLock.Release();
_syncAfter = DateTimeOffset.UtcNow.AddSeconds(secondsBetweenRetries);
}
}
if (_identityServerReady)
{
// Call the next delegate/middleware in the pipeline
await _next(context);
}
else
{
//As we did not succeeed, let's terminate return a 503 SERVICE UNAVAILABLE error back to the client
CreateServiceUnavailableResponse(context);
return;
}
}
/// <summary>
/// Create a service unavailable 503 error response
/// </summary>
/// <param name="context"></param>
private void CreateServiceUnavailableResponse(HttpContext context)
{
context.Response.Headers.Add("x-reason", "Waiting for IdentityServer");
context.Response.Headers.Add("Retry-After", "5"); //Add a retry again header, with 5 seconds
context.Response.Headers.Add("Cache-Control", "no-store,no-cache,max-age=0"); //Don't cache this response
context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; //503 status code
}
/// <summary>
/// Try to reach the IdentityServer discovery endpoint
/// </summary>
/// <returns>True if successfull</returns>
private async Task<bool> TryToReachIdentityServer(HttpContext context)
{
var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(httpRequestTimeout);
var response = await client.GetAsync(_discoveryUrl);
//Should we log?
if (response.IsSuccessStatusCode == false)
{
var secondsSinceStart = (int)DateTime.UtcNow.Subtract(_startTime).TotalSeconds;
Log.Logger.ForContext("SourceContext", "WaitForIdentityServerMiddleware")
.ForContext("DiscoveryUrl", _discoveryUrl)
.ForContext("Path", context.Request.Path)
.ForContext("Tried for over", secondsSinceStart.ToString() + " seconds")
.Information("Failed to reach IdentityServer at startup");
}
return response.IsSuccessStatusCode;
}
/// <summary>
/// Construct the discovery endpoint URL
/// </summary>
/// <param name="authority"></param>
/// <returns></returns>
private string buildDiscoveryUrl(string authority)
{
string Url = authority;
if (!Url.EndsWith("/", StringComparison.Ordinal))
{
Url = Url + "/";
}
Url = Url + ".well-known/openid-configuration";
return Url;
}
}
}
Then to use the handler:
//Make sure its placed before app.UseAuthentication();
//Wait for IdentityServer to startup
app.UseWaitForIdentityServer(new WaitForIdentityServerOptions()
{ Authority = _configuration["openid:authority"] });

Failed Email Delivery for some multi-lingual emails with MailKit while it works like a charm with legacy System.Net.Mail

We are using MailKit to send notification emails which can be in multi languages.
For languages like French, Japanese, and Spanish we are not able to deliver the emails. However, there is no failure in the SendAsync method in MailKit.
1- I noticed for French that it had to do with the accents in the email body. If the sentence happens to have no accents, the mail goes through to recipients. Otherwise, a failed delivery message with no explaination.
2- English and German text emails seem to work fine, except that they show in Junk email.
3- The messages are released to SMTP server and rejected there and returned to sender with mail delivery failed. No extra useful information.
A legacy system owned that also delivers email notifications does not have that problem, and by looking at their code, they are using the legacy System.Net.Mail not MailKit. That was the only difference. So I replaced our usage of MailKit with the legacy System.Net.Mail which we probably should not use based on message on the doc indicating that it is becoming obsolete, and it worked. The messages in all supported languages are being sent and they arrive in Inbox (not junk folder). No mail delivery failures whatsoever.
So I suspected that the way we construct the message might be problematic in MailKit.
In MailKit we used BodyBuilder to set the text as follows
private BodyBuilder FormatTextEncoding(EmailBodyType emailBodyType, string messageBody)
{
BodyBuilder builder = new BodyBuilder();
switch (emailBodyType)
{
case EmailBodyType.HTML:
builder.HtmlBody = messageBody;
break;
case EmailBodyType.TEXT:
builder.TextBody = messageBody;
break;
default:
_logger.LogWarning("new content type:{contentType}", emailBodyType);
builder.TextBody = messageBody;
break;
}
return builder;
}
The caller of this helper sets the body as follows
// set the message body
emailMessage.Body = bodyBuilder.ToMessageBody();
Within the GenerateEmailMessage function.
/// <summary>
/// Transforms consume result message to MimeMessage to be accepted by SMTP client
/// as ready message for sending.
/// </summary>
/// <param name="consumeResult"></param>
/// <returns></returns>
public EmailNotificationMessage GenerateEmailMessage(ConsumeResult<string, SendEmail> consumeResult)
{
// retrieve Kafka topic message
var msg = consumeResult.Message.Value;
// new email message to return
var emailMessage = new MimeMessage();
if(msg == null)
{
throw new Exception("[GenerateEmailMessage] Code bug. Message in Kafka topic cannot be null.");
}
// reference the email body
var emailBody = msg.detail.emailBody;
// based on content type, set Html body or Text body
// for bad content type, treat as text
// and create bodyBuilder
var bodyBuilder = FormatTextEncoding(emailBody.contentType, emailBody.messageBody);
// set the subject
emailMessage.Subject = msg.detail.subject;
// set the message body
emailMessage.Body = bodyBuilder.ToMessageBody();
// set the from list
foreach (var fromAddress in msg.detail.from)
{
emailMessage.From.Add(new MailboxAddress(fromAddress.displayName, fromAddress.emailAddress));
}
// check if sender is set and set the Sender value for MimeMessage
if (msg.detail.sender != null)
{
emailMessage.Sender = new MailboxAddress(msg.detail.sender.displayName, msg.detail.sender.emailAddress);
}
// set the recipients
foreach (var toAddress in msg.detail.to)
{
emailMessage.To.Add(new MailboxAddress(toAddress.displayName, toAddress.emailAddress));
}
// set replyTo field if different than from field
if (msg.detail.replyTo != null)
{
foreach (var replyToAddress in msg.detail.replyTo)
{
emailMessage.ReplyTo.Add(new MailboxAddress(replyToAddress.displayName, replyToAddress.emailAddress));
}
}
// set CC and BCC
if (msg.detail.cc != null)
{
foreach (var ccAddress in msg.detail.cc)
{
emailMessage.Cc.Add(new MailboxAddress(ccAddress.displayName, ccAddress.emailAddress));
}
}
if (msg.detail.bcc != null)
{
foreach (var bccAddress in msg.detail.bcc)
{
emailMessage.Bcc.Add(new MailboxAddress(bccAddress.displayName, bccAddress.emailAddress));
}
}
// set message importance
switch(msg.detail.messageImportance )
{
case MessageImportance.HIGH:
emailMessage.Importance = MimeKit.MessageImportance.High;
break;
case MessageImportance.LOW:
emailMessage.Importance = MimeKit.MessageImportance.Low;
break;
case MessageImportance.NORMAL:
emailMessage.Importance = MimeKit.MessageImportance.Normal;
break;
default:
_logger.LogWarning("unknown message importance: {importance}. Will not be set.", msg.detail.messageImportance);
break;
}
// set message priority
switch (msg.detail.messagePriority)
{
case MessagePriority.URGENT:
emailMessage.Priority = MimeKit.MessagePriority.Urgent;
break;
case MessagePriority.NORMAL:
emailMessage.Priority = MimeKit.MessagePriority.Normal;
break;
case MessagePriority.NON_URGENT:
emailMessage.Priority = MimeKit.MessagePriority.NonUrgent;
break;
default:
_logger.LogWarning("unknown message priority: {priority}. Will not be set.", msg.detail.messagePriority);
break;
}
// set X-message priority
switch (msg.detail.xMessagePriority)
{
case XMessagePriority.HIGH:
emailMessage.XPriority = MimeKit.XMessagePriority.High;
break;
case XMessagePriority.HIGHEST:
emailMessage.XPriority = MimeKit.XMessagePriority.Highest;
break;
case XMessagePriority.LOW:
emailMessage.XPriority = MimeKit.XMessagePriority.Low;
break;
case XMessagePriority.LOWEST:
emailMessage.XPriority = MimeKit.XMessagePriority.Lowest;
break;
case XMessagePriority.NORMAL:
emailMessage.XPriority = MimeKit.XMessagePriority.Normal;
break;
default:
_logger.LogWarning("unknown message priority: {priority}. Will not be set.", msg.detail.xMessagePriority);
break;
}
EmailNotificationMessage message;
var messageId = msg.identifier.eventId;
var instanceId = EmailNotificationMessage.DEFAULT_CUSTOMER_INSTANCE_ID;
if (msg.meta.instanceId.HasValue)
{
instanceId = msg.meta.instanceId.Value;
}
if (msg.identifier.eventId == null)
{
// generate globally unique message ID
messageId = MimeKit.Utils.MimeUtils.GenerateMessageId();
message = new EmailNotificationMessage(emailMessage,
messageId, msg.identifier.eventType, instanceId);
}
else
{
// pass through the message ID
message = new EmailNotificationMessage(emailMessage,
messageId, msg.identifier.eventType, instanceId);
}
return message;
}
/// <summary>
/// Handles the consume result message.
/// Calls GetEmailMessage to transform to message acceptable by SMTP client
/// Calls SendEmailAsync
/// </summary>
/// <param name="consumeResult"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task HandleMessageAsync(ConsumeResult<string, SendEmail> consumeResult, CancellationToken cancellationToken)
{
// cancel upon cancellation request
cancellationToken.ThrowIfCancellationRequested();
// convert Kafka message to Email message format for SMTP service
var emailMessage = GenerateEmailMessage(consumeResult);
// send the email
await SendEmailAsync(emailMessage, cancellationToken);
}
/// <summary>
/// Uses the SMTP client prototype or interface to SendMessageAsync
/// </summary>
/// <param name="message"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task SendEmailAsync(EmailNotificationMessage message, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
await _smtpClient.SendMessageAsync(_config, message, cancellationToken);
_logger.LogInformation("Sending email with {EmailNotificationMessage}", message.Format());
}
And here is how we send the message finally
/// <summary>
/// Uses the MailKit SMTP client to send message to SMTP Server.
/// Generates short-living client for each message
/// Connects to SMTP server first. Then attempts to Send a message.
/// Then disconnects from SMTP server to avoid leaking connections.
/// Then the SMTP client is disposed appropriately
/// </summary>
/// <param name="config"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendMessageAsync(EmailConfiguration config, EmailNotificationMessage message, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var client = _smtpClientFactory() )
{
await client.ConnectAsync(config.SMTPServer, config.Port, false, cancellationToken);
await client.SendAsync(message.Message, cancellationToken);
await client.DisconnectAsync(true, cancellationToken);
}
}
}
We can always just revert to using System.Net.Mail but since it is recommended by Microsoft not to use, it does not seem right. What might be going wrong here? Could it also be a problem with the legacy SMTP IIS server not able to accept these MailKit messages? Or are we constructing the email body wrong?
NOTE: I also tried skipping all that encoding SetText logic and directly set the bodyBuilder.HtmlText and Text to the message string. That also didn't work.
Thanks for reading and I appreciate the help in advance.
EDIT
This code using System.Net.Mail works just fine. It was quick code as test to check if swapping MailKit can resolve this.
notice how it retrieved the TextPart and other MimeMessage components to generate MailMessage component for the legacy Smtp Client.
Here is an example failure report for message in French
EDIT 2
Added Protocol Logs for a message that fails to deliver
EDIT 3
For comparison purposes here is an image of the logs for a successful English message:
The core of the problem turned out to be that the messages being sent out were 8-bit (e.g. Content-Transfer-Encoding: 8bit) and even though the Postfix SMTP server support the 8BITMIME extension, when Postfix went on to relay the message to an IIS SMTP server, the IIS SMTP server did not support the 8BITMIME extension and so Postfix was unable to deliver the message.
The solution was to call message.Prepare (EncodingConstraint.SevenBit); to force MimeKit to calculate the best encoding to use for 8bit MIME part and either quoted-printable or base64 encode them.

Custom ServiceStack OAuth2 provider

We are trying to communicate with a REST server, which uses its own OAuth2 implementation.
This server is written by another company in Java, so I don't have much influence about it.
I've got all the necessary information, like Access Token URL, Refresh URL, Client Id, Client Secret, etc. I can already request an access token and then request some other data from this server, using the REST client Postman.
Now I'd like to use the ServiceStack client (version 4.5.14), to communicate with this server in C# .NET 4.6.2.
My problem is: All the examples I found, e.g. http://docs.servicestack.net/authentication-and-authorization#custom-authentication-and-authorization are either about the server-side or about authentication against Facebook or Google.
I already implemented my own CustomOAuth2Provider, setting the access token URL, ConsumerSecret, etc.
But how do I tell the JsonServiceClient, to use this Provider, before executing the specific request?
Thank you,
Daniel
Edit:
I read a lot of documentation and ServiceStack sourcecode, and I think my main problems are the following:
I abuse the ServiceStack Client to communicate with a non-ServiceStack application, which I can not modify.
Maybe the OAuth2 implementation of the third-party application is not 100% correct, as it expects authorization and token request in the same request.
But I got it working now and would like to show my solution here.
It still can be improved, e.g. it does not use the received refresh token right now.
public class ThirdPartyAuthenticator : IDisposable
{
// TODO: Move to config
public const string AccessTokenUrl = "";
public const string ConsumerKey = "";
public const string ConsumerSecret = "";
public const string Username = "";
public const string Password = "";
/// <summary>
/// Remember the last response, instance comprehensive so we do not need a new token for every request
/// </summary>
public static ServiceModel.ThirdPartyOAuth2Response LastOAuthResponse = null;
/// <summary>
/// This already authenticated client can be used for the data requests.
/// </summary>
public JsonServiceClient AuthenticatedServiceClient { get; set; }
public ThirdPartyAuthenticator()
{
if (LastOAuthResponse == null || (LastOAuthResponse.ExpiryDateTime < DateTime.Now)) // TODO: Use Refresh token?
{
// Get token first
JsonServiceClient authClient = new JsonServiceClient(AccessTokenUrl);
authClient.UserName = ConsumerKey;
authClient.Password = ConsumerSecret;
authClient.AlwaysSendBasicAuthHeader = true;
var request = new ServiceModel.ThirdPartyOAuth2Request();
request.Username = Username;
request.Password = Password;
// Use the Get URI, because server expects username + password as query parameter
LastOAuthResponse = authClient.Post<ServiceModel.ThirdPartyOAuth2Response>(request.ToGetUrl(), request);
}
// If no exception was thrown, we have a valid token here.
AuthenticatedServiceClient = new JsonServiceClient(AccessTokenUrl);
AuthenticatedServiceClient.BearerToken = LastOAuthResponse.AccessToken;
}
public void Dispose()
{
AuthenticatedServiceClient?.Dispose();
}
}
usage:
using (var foo = new ThirdPartyAuthenticator())
{
var response = foo.AuthenticatedServiceClient.Get(new ServiceModel.GetMyData() { SomeId = 10 });
}
OAuth providers require a browser to redirect to the OAuth provider site where Users are able to accept authentication with the App and any permissions it requires. Once the user accepts they're redirected back to your ServiceStack App where it will create an Authenticated User Session. The session id from the Authenticated User Session is what's configured on the ServiceStack client to establish authenticated requests.
Here are some Example Apps which use OAuth to Authenticate using a browser then capture the browser redirect to extract the session cookies and configure it on the C# Service Client where they're then able to make Authenticated requests:
https://github.com/ServiceStackApps/TechStacksAuth
https://github.com/ServiceStackApps/AndroidJavaChat

How to secure my web api in mvc 4

http://localhost:1075/api/customer/Getcustomer?id=1
1.Above url get me result of perticular user profile information.. following is controller code.This web api called and designed for android application.but even if I called it from my browser or postman tool it gives me result. then where is security?? If anyone knows this url then he can access all our sesitive information. Please suggest me how can secure my web api...
public customerCollection Getcustomer(int id)
{
db.Configuration.ProxyCreationEnabled = false;
var customer = (from c in db.customers
where c.customerID == id
select c).ToArray();
var result = new customerCollection();
if (customer == null)
{
result.status = "failed";
}
result.status = "success";
result.customerArray = customer;
return result;
}
And response is
{
"customerArray": [
{
"customerID": 1,
"cname": "Yogesh",
"cmobile": "9970714878",
"cemail": "yogeshkhurpe11#gmail.com",
"cpassword": "yogesh",
"addressLine1": "balaji hostel",
"addressLine2": "Jadhav nagar",
"area": "Vadgoan",
"pincode": "411041",
"cimage": "Na",
"cdate": "2017-06-28T14:16:03",
"cstatus": "active",
"orders": []
}
],
"status": "success"
}
Here is the step by step way to use authentication.
You should create an Authorize attribute in you WebAPI to Authorize an incoming request first.
First of all the user who is going to use your API will authenticate himself using username and password, once this process gets completed and user has been authorized you should return a token to the user for further requests. This token can be the encrypted time or GUID or anything whatever you prefer to know about the particular user, that for whom you have generated that token. You can save that token into the database for further uses.
Now next time when user will try to access your API, he will send the token in the header section of that api request. If the token is valid you should return the results else you should return an error stating that token is invalid.
Thanks
Hope this helps!
Try the following logic.
First create an Authorize attribute in you WebAPI.
Use the Authorize attribute above your api function to protect the api's
public class AuthenticationFilter : AuthorizationFilterAttribute
{
/// <summary>
/// read requested header and validated
/// </summary>
/// <param name="actionContext"></param>
public override void OnAuthorization(HttpActionContext actionContext)
{
var identity = FetchFromHeader(actionContext);
if(identity != null)
{
//get the user based on the identity
var user=TokenService.getUserFromToken(identity);
if (user)
{
CurrentThread.SetPrincipal(new GenericPrincipal(new GenericIdentity(user), null), null, null);
}
else
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
return;
}
}
else
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
return;
}
base.OnAuthorization(actionContext);
}
/// <summary>
/// retrive header detail from the request
/// </summary>
/// <param name="actionContext"></param>
/// <returns></returns>
private string FetchFromHeader(HttpActionContext actionContext)
{
string requestToken = null;
var authRequest = actionContext.Request.Headers.Authorization;
if (authRequest != null && !string.IsNullOrEmpty(authRequest.Scheme) && authRequest.Scheme == "Basic")
requestToken = authRequest.Parameter;
return requestToken;
}
}

ERROR: PayPal: Prop is required: payment

I am running into an issue that I do not completely understand. Following this demo from the PayPal developer site: https://developer.paypal.com/demo/checkout/#/pattern/server
I am running into the error in the title of this post.
Here are some code samples
Client side:
payment: function () {
// Make a call to the merchant server to set up the payment
return paypal.request.post('My/api/call').then(function (res) {
return res.token;
});
},
Server side (my/api/call)
var createdPayment = payment.Create(apiContext);
return createdPayment;
I am using the PayPal-NET-SDK to create these objects and return them to which PayPal seems to be OK with until the response is returned. The demo code from PayPal, I think, implies that a payment object is returned. Which is what I am returning from the server (PayPal gives it an ID, a token, etc from the api call), granted the property name of the token is different. Does anyone have any insight into what may be going on?
Thanks
EDIT: Asa per request here is the payment.Create method
/// <summary>
/// Creates and processes a payment. In the JSON request body, include a `payment` object with the intent, payer, and transactions. For PayPal payments, include redirect URLs in the `payment` object.
/// </summary>
/// <param name="apiContext">APIContext used for the API call.</param>
/// <returns>Payment</returns>
public Payment Create(APIContext apiContext)
{
return Payment.Create(apiContext, this);
}
/// <summary>
/// Creates (and processes) a new Payment Resource.
/// </summary>
/// <param name="apiContext">APIContext used for the API call.</param>
/// <param name="payment">Payment object to be used in creating the PayPal resource.</param>
/// <returns>Payment</returns>
public static Payment Create(APIContext apiContext, Payment payment)
{
// Validate the arguments to be used in the request
ArgumentValidator.ValidateAndSetupAPIContext(apiContext);
// Configure and send the request
var resourcePath = "v1/payments/payment";
var resource = PayPalResource.ConfigureAndExecute<Payment>(apiContext, HttpMethod.POST, resourcePath, payment.ConvertToJson());
resource.token = resource.GetTokenFromApprovalUrl();
return resource;
}
You need to return either the EC-XXXXXXX token or the PAY-XXXXXX id, as a string, not the entire payment object.

Categories