I am creating a WebApi Service that interacts with the Yahoo! API that uses OAuth 1.0. I am able to use MVC to call and get the access token back with no issue. I am experimenting in doing the process without using MVC at all. This is what I would like to occur.
Call Api through the browser (http://mysite/api/auth/AuthentiateWithYahoo)
New tab is opened up to auth page, access is given.
Redirect is done to another method on the controller and the access token is stored.
Here is the code that currently works with MVC.
public ActionResult AuthenticateWithYahoo()
{
var callbackUri = new Uri ("http://localhost/yahoo/api/auth/Home/YahooOAuthCallback");
try
{
//this call eventually does this
//var request = this.Consumer.PrepareRequestUserAuthorization(callback, null, null);
//this.Consumer.Channel.Respond(request);
this.Fantasizer.BeginAuthorization(callbackUri);
}
catch (ProtocolException pe)
{
var webException = pe.InnerException as WebException;
if (webException != null)
{
HttpWebResponse response = webException.Response as HttpWebResponse;
if (response != null && response.StatusCode == HttpStatusCode.Unauthorized)
{
var appException =
new ApplicationException(
"Unable to authorize with Yahoo. Check YahooConsumerKey and YahooConsumerSecret environment variables.",
pe);
throw appException;
}
}
throw;
}
// This will not get hit
return null;
}
public ActionResult YahooOAuthCallback()
{
// this ends up calling OAUTH complete authorization
this.Fantasizer.CompleteAuthorization();
return RedirectToAction("ListLeagues", "User");
}
What would I need to do to move this process to one that is not dependent on MVC? If possible, passing the access token back in the call would be ideal. Let me know if more information is required.
Related
I'm working on .NET Maui mobile client with ASP.NET Core backend, and am trying to integrate Google authorization via the Web Authenticator API built into Maui. I was able to get the login to function by basically following the guide on the Web Authenticator page linked above, but haven't been able to logout from the account I originally logged in with. I've created a logout endpoint for my server, and after I hit this endpoint (where I call HttpContext.SignOutAsync() ) and compare the value of ClaimsPrinciple, the data from Google is gone (see attached screenshots).
before logout
after logout
However, the next time I try to log in I do not need to go through Google authentication, it automatically logs me in again. I've seen similar issues with Web Authenticator (linked here and here and in some other issues linked to these). I'm new at this framework and mobile dev in general and I'm still unclear from these resources what the best way to handle this is - most of these issues are also relating to Xamarins forms rather than Maui, so not sure if theres any more updated solution.
These are the implementations of login and logout and the corresponding requests
public class MobileAuthController : ControllerBase
{
const string callbackScheme = "myapp";
[HttpGet("{scheme}")]
public async Task Get([FromRoute] string scheme)
{
var auth = await Request.HttpContext.AuthenticateAsync(scheme);
if (!auth.Succeeded
|| auth?.Principal == null
|| !auth.Principal.Identities.Any(id => id.IsAuthenticated)
|| string.IsNullOrEmpty(auth.Properties.GetTokenValue("access_token")))
{
//Not authenticated, challenge
await Request.HttpContext.ChallengeAsync(scheme);
}
else
{
var claims = auth.Principal.Identities
.FirstOrDefault().Claims.Select(claim => new
{
claim.Issuer,
claim.OriginalIssuer,
claim.Type,
claim.Value
});
//Build the result url
var user = this.User; //CHECK VALUE OF USER
var url = callbackScheme + "://#";
//Redirect to final url
Request.HttpContext.Response.Redirect(url);
}
}
[HttpPost("logout")]
public async Task Logout()
{
try
{
await HttpContext.SignOutAsync();
var url = callbackScheme + "://#";
var user = this.User; //CHECK VALUE OF USER
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to sign out user {ex.Message}");
}
}
var url = "http://localhost:5000/mobileauth/logout";
HttpContent content = null;
var response = await httpClient.PostAsync(url, content);
WebAuthenticatorResult authResult = await WebAuthenticator.Default.AuthenticateAsync(
new Uri("https://localhost:5001/mobileauth/Google"),
new Uri("myapp://"));
Edit - I'm primarily confused on how i need to be handling Cookies and access/refresh tokens and the built in claims stuff - like I said, I'm new at this
I am building a web API that will serve as a connector between a 3rd-party application and mine.
This application will be running on a server and will be receiving POST requests from the 3rd-party application and sending POST requests of its own as a response.
Before it starts sending these requests, my web API needs to make a POST to the 3rd-party service, so it can be registered and received an authorization token, that it will be used on the requests it sends back, kinda similar to an OAuth token, from what I understand.
Since my code is all inside an HttpPost method, it only gets activated when it receives a call, and that part work as expected. When the service is authenticated and is receiving requests, is fine. The problem is when my service or the 3rd-party is restarted or something, the current token is made invalid or lost and a new one needs to be requested again.
What I wish to do is make that the call to register my service and receive the token is sent when the service starts, automatically.
Currently I am doing a manual call to trigger when my service needs to be registered, but that make it necessary for me to be at my computer to do so, and the connection is not make until I call that request.
Here is a sample of my code:
public class Controller : ApiController
{
static string SessionToken = "";
[HttpPost]
[Route("connector/webhook")]
public async Task<HttpStatusCode> Webhook(UpdateContentRequestBody body)
{
var NO_ERROR = 0;
try
{
if (string.IsNullOrEmpty(SessionToken))
{
// This registers my service.
var registerConector = ConectorOSCCApi.RegisterConector();
if (respostaRegistrarConector.ErrorCode != NO_ERROR)
{
throw new Exception();
}
SessionToken = registerConector.SessionToken;
}
ConectorApi.KeepAliveRequest(SessionToken);
RepeatKeepAlive();
ProccessDataAndSendResponseRequest(body);
return HttpStatusCode.OK;
}
catch (Exception e)
{
SessionToken = "";
return HttpStatusCode.InternalServerError;
}
I want the method to register the service to run without the need of a call to "connector/webhook", but the rest of the processing and response to only happens when such a call is received. How can I do that?
EDIT:
My code is inside a ASP.NET Web Application.
I am using .NET Framework 4.5 and hosting my web application on IIS.
This should do it for you :)
public class Controller : ApiController
{
static string _sessionToken = "";
static string SessionToken
{
get
{
if (string.IsNullOrEmpty(_sessionToken))
{
InitToken();
}
return _sessionToken
}
}
void InitToken()
{
if (string.IsNullOrEmpty(_sessionToken))
{
// This registers my service.
var registerConector = ConectorOSCCApi.RegisterConector();
if (respostaRegistrarConector.ErrorCode != NO_ERROR)
{
throw new Exception();
}
_sessionToken = registerConector.SessionToken;
}
}
public Controller() : base()
{
InitToken();
// anything else
}
[HttpPost]
[Route("connector/webhook")]
public async Task<HttpStatusCode> Webhook(UpdateContentRequestBody body)
{
var NO_ERROR = 0;
try
{
ConectorApi.KeepAliveRequest(SessionToken);
RepeatKeepAlive();
ProccessDataAndSendResponseRequest(body);
return HttpStatusCode.OK;
}
catch (Exception e)
{
SessionToken = "";
return HttpStatusCode.InternalServerError;
}
}
}
You don't need to wait for a request to your service to request a token.
Prerequisites : make sure you know what error code you receive from the third party API if your token is no longer correct.
When your API initializes, you will have a method available, ApplicationStart or something else in Startup.cs, depending on version, setup etc. Use that method to request the token from the third party API. Cache the token in the application level cache.
An example of caching can be found here: Caching Data in Web API
When your application receives a request, grab the token from the cache and issue the call to the third part API. If everything works, happy days. If it fails with token issue error code, then re-issue the token request and try again this time with the fresh token. Replace the cached token with the new one.
So basically, keep using a token until it fails, then automatically request a new one and update it. This way you don't need to be there to request the token manually.
You could wrap up this token logic into a service class so you don't have a lot to do in the endpoints.
At Backend, I have used asp.net web API and I can validate the token by comparing hidden field token and cookies token as shown below:
try
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
CookieHeaderValue cookie = Request.Headers
.GetCookies(AntiForgeryConfig.CookieName)
.FirstOrDefault();
if (cookie != null)
{
Stream requestBufferedStream = Request.Content.ReadAsStreamAsync().Result;
requestBufferedStream.Position = 0;
NameValueCollection myform = Request.Content.ReadAsFormDataAsync().Result;
try
{
AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value,
myform[AntiForgeryConfig.CookieName]);
}
catch (Exception ex)
{
throw new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
}
}
But I am not able to create XSRF - token at the frontend using Vue?
How to create this XSRF validation token at the frontend so that I can later send them back to the backend for validation.
Here, My backend and frontend projects are separate projects.
Anyone there...
The XSRF token has to be created in backend and stored in cookie o local storage in the frontend side.
Check the MS documentation to see examples in vainilla javascript and angular (Vue has to be similar) https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-2.2#javascript-ajax-and-spas
I recently implemented custom authentication with Azure Mobile App - All the server side works fine and also my web application which is using that mobile app service is working fine. I tested the server-side in details with POSTMAN and with different scenarios, everything works fine until I try to LoginAsync on Xamarin.
When I pass email and password in POSTMAN, I get the following response as a clear indication that it is working
but when I send a request from my app using LoginAsync I get the following error.
Cannot access child value on Newtonsoft.Json.Linq.JValue
My code to send request is fairly simple as following
public async Task<bool> Authenticate()
{
string username = "todo#gmail.com";
string password = "todo";
string message = string.Empty;
var success = false;
var credentials = new JObject
{
["email"] = username,
["password"] = password
};
try
{
MobileServiceUser user = await client.LoginAsync("CustomAuth", credentials);
if (user != null)
{
success = true;
CreateAndShowDialog("OK", "Auth");
}
}
catch (Exception ex)
{
CreateAndShowDialog(ex, "Auth Error");
}
return success;
}
where I am calling it as follows
private MobileServiceClient client;
client = new MobileServiceClient(applicationURL);
await Authenticate();
Any idea why I am getting Cannot access child value on Newtonsoft.Json.Linq.JValue error?
Cheers
EDIT POST
As a workaround, I am temporarily using InvokeApiAsync with JObject.FromObject instead of LoginAsync
await client.InvokeApiAsync("/.auth/login/CustomAuth", JObject.FromObject(credentials), HttpMethod.Post, null);
I am still not sure why LoginAsync does not work - Until I find a solution I will keep using InvokdeApiAsync as a workaround
AFAIK, your initialization for credentials is correct. For the below error:
Cannot access child value on Newtonsoft.Json.Linq.JValue
I checked your testing result via POSTMAN and found that you did not return userId to your client. The essential properties returned to your client would look like as follows:
{
"authenticationToken":"***",
"user":{
"userId":"***"
}
}
When using MobileServiceClient.LoginAsync, the client SDK would internally invoke LoginAsync() method under MobileServiceAuthentication.cs as follows:
JToken authToken = JToken.Parse(response);
// Get the Mobile Services auth token and user data
this.Client.CurrentUser = new MobileServiceUser((string)authToken["user"]["userId"]);
this.Client.CurrentUser.MobileServiceAuthenticationToken = (string)authToken[LoginAsyncAuthenticationTokenKey];
You would find that it would try to extract the userId property under user to construct the MobileServiceUser instance and assign to MobileServiceClient.CurrentUser.
My problem: When a session expires user still can perform an action (search). Action results are some garbage (controller is not visited). I don't know why; I just want to redirect a user to login page.
My plan is to make custom Authorize and override HandleUnauthorizedRequest(HttpActionContext) and redirect a user to index.
I have no idea how to redirect to my default page.
Sample code:
public class SessionTimeoutAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
base.HandleUnauthorizedRequest(actionContext);
//redirect here
}
}
You're looking to set Response of actionContext to Unauthorized http response. Here's a sample how to do so.
public class SessionTimeoutAttribute: AuthorizeAttribute {
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) {
base.HandleUnauthorizedRequest(actionContext);
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
}
This should hopefully redirect user to the page you (hopefully) defined in CookieAuthenticationOptions.
Edit: To be honest, this kind of defined the purpose of Web API if you let users access it in the same way as you would enter a normal page. You should on the client determine if response from the web api endpoint was success, unathorized etc. You should be returning that instead of direct redirect from the web api.
If you really wanted to redirect, you might try the following...
var response = actionContext.Request.CreateResponse(HttpStatusCode.Redirect);
response.Headers.Location = new Uri("https://www.stackoverflow.com");
actionContext.Response = response;
Again, I don't see the point in redirecting from Web API. It should only return the requested data/errors. You should be processing it else where.
I thought I can redirect to site directly from Web API... Well, I can't. Turned out I don't need custom Authorize, because [Authorize] redirect correctly. I need my client to redirect when Authorize wants. In my case (Angular 1,5) it was an interceptor.
app.service('httpRedirectInterceptor', ['$window', function ($window) {
this.response = function (redirection) {
if (typeof redirection.data === 'string') {
if (redirection.data.indexOf instanceof Function &&
redirection.data.indexOf('id="app-login-page"') != -1) {
$window.location.pathname = "/Account/Login";
}
}
return redirection;
};
this.request = function (req) {
var elem = angular.element(document.body).find('div[ncg-request-verification-token]').attr('ncg-request-verification-token');
req.headers['RequestVerificationToken'] = elem || "no request verification token";
return req;
};}]);
and in app.config
$httpProvider.interceptors.
push('httpRedirectInterceptor');