I am using Restsharp within an MVC app, trying to call a backend MVC WebAPI protected by Thinktecture IdentityModel AuthenticationConfiguration.
MVC API Setup
My MVC API test is setup with the below:
private static void ConfigureAuth(HttpConfiguration config)
{
var authConfig = new AuthenticationConfiguration
{
DefaultAuthenticationScheme = "Basic",
EnableSessionToken = true,
SendWwwAuthenticateResponseHeader = true,
RequireSsl = false,
ClaimsAuthenticationManager = new AddCustomClaims(),
SessionToken = new SessionTokenConfiguration
{
EndpointAddress = "/token",
SigningKey = Convert.ToBase64String(CryptoRandom.CreateRandomKey(32)),
DefaultTokenLifetime = new TimeSpan(1, 0, 0)
}
};
authConfig.AddBasicAuthentication((username, password) =>
{
return username == "admin" && password == "password";
});
config.MessageHandlers.Add(new AuthenticationHandler(authConfig));
}
private static void ConfigureCors(HttpConfiguration config)
{
var corsConfig = new WebApiCorsConfiguration();
config.MessageHandlers.Add(new CorsMessageHandler(corsConfig, config));
corsConfig
.ForAllOrigins()
.AllowAllMethods()
.AllowAllRequestHeaders();
}
Javascript works OK
I know 100% the token I am sending with Restsharp is correct and working with equivalent json calls (the token used in the javascript is the same used in the Web MVC controller as its stored in the Session array):
var authToken = config.authToken,
baseUri = config.baseUri,
configureRequest = function (xhr) {
xhr.setRequestHeader("Authorization", "Session " + authToken);
},
errorHandler = function (xhr, status, error) {
if (xhr.status === 401 && config.onAuthFail) {
config.onAuthFail(xhr, status, error);
}
};
Calling the API from my MVC web front end client app - Authorization has been denied for this request
Then in my MVC app controller action i use RestSharp as follows:
public ActionResult Test()
{
var token = Session[Constants.SessionTokenKey] as string;
var client = new RestClient(new Uri("http://localhost:65104/"));
var request = new RestRequest("contacts", Method.GET);
string authHeader = System.Net.HttpRequestHeader.Authorization.ToString();
request.AddHeader(authHeader, string.Format("Authorization Session {0}", token));
var json = client.Execute(request);
// break point here checking the status it has been denied
return View("Index");
}
Checking the status, it returns "{\"message\":\"Authorization has been denied for this request.\"}".
I have tried adding the token with Restsharp request methods with request.AddHeader(authHeader, string.Format("Authorization Session {0}", token)); and also with request.AddHeader(authHeader, string.Format("JWT {0}", token));, but get the same access denied for both ways.
What am I doing wrong please or any recommendations on where to look?
Looks like your JavaScript code and RestSharp request code doesn't match.
In JS you set a header with name Authorization and give it a value Session sometoken:
xhr.setRequestHeader("Authorization", "Session " + authToken);
In RestSharp you assign a header with name Authorization a value Authorization Session sometoken
request.AddHeader(authHeader, string.Format("Authorization Session {0}", token));
So I would suggest changing your RestSharp AddHeader code to this:
request.AddHeader(authHeader, string.Format("Session {0}", token));
Related
I have just started to use Asp.Net Core and I managed to create a mvc project. In This project I have created an API and it is secured with token based authorization.I have also used identity framework for user auhentication. Now I want to consume this API to perform CRUD operations with passing token but have no clear idea how to do that. After searching similar questions what I have tried is generate the token using user credentials (username, password) when user successfully logged in or registered and attach the generated token to header and as far as I know it will be passed through each subsequent request.
First I tried creating a method to call to generate the token after success login or registration. This includes in same controller which used for login and registration.
Token generate method
public string GenerateAuthToken(ApplicationUser applicationUser)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT")["TokenSignInKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(type:JwtRegisteredClaimNames.Sub, applicationUser.Id),
new Claim(type:JwtRegisteredClaimNames.Email, applicationUser.Email),
new Claim(type:JwtRegisteredClaimNames.Iat,
value:DateTime.Now.ToUniversalTime().ToString())
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var stringToken = tokenHandler.WriteToken(token);
return stringToken;
}
I call this after success user login and register,
public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = registerViewModel.Username,
Email = registerViewModel.Email};
var result = await _userManager.CreateAsync(user, registerViewModel.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
var token = GenerateAuthToken(user);
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("bearer", token);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "User Registration Failed");
}
return View(registerViewModel);
}
When this executed, the token is successfully generated but does not attach the token. I do not know if I am doing any wrong here. But I found someone facing the same issue but has tried different way to achieve this. I think it is the correct way but not sure. Instead of generate the token on success login, have to generate it each api call. According to this solution I created another controller and action to generate the token.
public async Task<IActionResult> GetToken([FromBody] AuthViewModel authViewModel)
{
var user = _context.Users.FirstOrDefault(u => u.Email == authViewModel.Email);
if (user != null)
{
var signInResult = await _signInManager.CheckPasswordSignInAsync(user,
authViewModel.Password, false);
if (signInResult.Succeeded)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT")
["TokenSignInKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(type:JwtRegisteredClaimNames.Sub,authViewModel.Email),
new Claim(type:JwtRegisteredClaimNames.Email,
authViewModel.Email),
new Claim(type:JwtRegisteredClaimNames.Iat,
value:DateTime.Now.ToUniversalTime().ToString())
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new
SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var stringToken = tokenHandler.WriteToken(token);
return Ok(new { Token = stringToken });
}
return BadRequest("Invalid User");
}}
AuthViewModel
public class AuthViewModel
{
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
I added authViewModel to accept logged user credentials since I don't want add them manually, Then I have created another controller to perform the CRUD same as the above mentioned link Please note that I followed the solution mentioned below that page.
private async Task<string> CreateToken()
{
var user = await _userManager.GetUserAsync(User);
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:7015/Auth");
request.Content = JsonContent.Create(new AuthViewModel{
Email = user.Email, Password = user.PasswordHash
});
var client = _clientFactory.CreateClient();
HttpResponseMessage response = await client.SendAsync(request);
var token = await response.Content.ReadAsStringAsync();
HttpContext.Session.SetString("JwToken", token);
return token;
}
request.Content I added to match my solution since token should be generated using user credentials. But I have no idea how to pass the logged in user's credentials with the request. This does not work. It is not possible to access the user password.
This is how I called the token generate action to perform CRUD. And I use JQuery Ajax to call the GetAllSales endpoint.
public async Task<IActionResult> GetAllSales()
{
string token = null;
var strToken = HttpContext.Session.GetString("JwToken");
if (string.IsNullOrWhiteSpace(strToken))
{
token = await CreateToken();
}
else
{
token = strToken;
}
List<Sale> sales = new List<Sale>();
var client = _clientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get,
"http://localhost:7015/api/Sales");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await client.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var apiString = await response.Content.ReadAsStringAsync();
sales = JsonConvert.DeserializeObject<List<Sale>>(apiString);
}
Ok(sales);
}
This does not work. An exception throws
'System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)'
Please suggest me and show me how to achieve this correctly. I am expecting all of your help. Thank you.
System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'
This issue means you inject IHttpClientFactory in AccessApiController without registering the service in Program.cs.
Register IHttpClientFactory by calling AddHttpClient in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
I have a Umbraco website that has google sign in button configured as follows:
At the top of the page (inside the header section) I have the scripts for calling google API:
<script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
<script>
function start() {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init({
client_id: '<myapp client Id>.apps.googleusercontent.com',
// Scopes to request in addition to 'profile' and 'email'
redirect_uri: 'http://localhost:40136/umbraco/Surface/AuthSurface/GoogleAuthrizedUser',
scope: 'profile email'
});
});
}
</script>
In the body section of the code I have the google button setup and associated click function:
<script>
function onSignIn(authResult) {
if (authResult['code']) {
var authCode = authResult['code'];
console.log("Authorization Code: " + authCode);
$.post("/umbraco/Surface/AuthSurface/GoogleAuthrizedUser", { code: authCode })
.done(function(msg) {
// Success settings
})
.fail(function(xhr, status, error) {
});
} else {
//authResult['code'] is null
//handle the error message.
}
};
</script>
Controller code that handles the call back on the server end:
public class AuthSurfaceController : SurfaceController
{
public ActionResult GoogleAuthrizedUser()
{
string AuthCode = HttpContext.Request["code"];
var info = new GoogleAccessTokenResponse();
var client = new GoogleOAuthClient();
try
{
info = client.GetAccessTokenFromAuthorizationCode(AuthCode);
}
catch (Exception ex)
{
var strMessage = String.Format("<div class=\"info\"><p>{0}</p><p>{1}</p></div>", "Google Login Error",
ex.Message);
return Json(new AjaxOperationResponse(false, strMessage));
}
}
}
On the Serverside I am using Skybrud Social plugin for accessing google apis.
The google authentication happens in the popup and authorizes client with credentials and authResult['code'] has a valid code.
In the controller when I initialize the client and call the function GetAccessTokenFromAuthorizationCode(AuthCode), it returns an exception of 'Invalid Request'
I tried checking this authResult['code'] returned in the javascript function onSignIn in the https://developers.google.com/oauthplayground/
Same error description is shown 'Invalid request'. I am not sure why this is happening. The error returned is "invalid_grant"
Can anyone have a solution to this problem? What am I doing wrong here?
In your surface controller you're initializing a new instance of GoogleOAuthClient, but without setting any of the properties. The GetAccessTokenFromAuthorizationCode method requires the ClientId, ClientSecret and RedirectUri properties to have a value. You can initialize the properties like this:
// Initialize a new instance of the OAuth client
GoogleOAuthClient oauth = new GoogleOAuthClient {
ClientId = "The client ID of your project",
ClientSecret = "The client secret of your project",
RedirectUri = "The return URI (where users should be redirected after the login)"
};
You can read more about authentication in the documentation: http://social.skybrud.dk/google/authentication/ (the approach explained there will however not use any JavaScript)
I'm using ASP.NET MVC 6 (.net core). With it, i'm using the built in external login logic in order to authenticate with facebook.
I've made a modification to it so that instead of authenticating within the same window, i'm launching a popup and authenticating there. Once successful, the popup closes itself and tells my main window to redirect. This all works.
However, I want to use the "smaller/mini" version of the facebook login page. This can be seen here:
https://www.facebook.com/login.php?display=popup
"display=popup" is what is controlling it.
I don't see how i can inject this kvp in my C# code. Where can i do it?
app.UseFacebookAuthentication(new FacebookOptions
{
// was hoping for something here... tried to stick it into the authorizationurl but then i end up with 2 question marks and it fails
AppId = "blah",
AppSecret = "blah"
});
[AllowAnonymous]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
// Don't see anything here...
return Challenge(properties, provider);
}
You can use OnRedirectToAuthorizationEndpoint event:
var facebookOptions = new FacebookOptions
{
AppId = "",
AppSecret = "",
Events = new OAuthEvents()
{
OnRedirectToAuthorizationEndpoint = ctx =>
{
ctx.HttpContext.Response.Redirect(ctx.RedirectUri + "&display=popup&pip");
return Task.FromResult(0);
}
}
};
app.UseFacebookAuthentication(facebookOptions);
I'm trying to upload an video to my YouTube account with the following code in my ActionResult in my asp.net MVC project:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Upload([Bind(Include = " Title, Description")] HttpPostedFileBase uploadFile, Videos videos)
{
var credential = AuthYouTube();
YouTubeService youtubeService = new YouTubeService(new
YouTubeService.Initializer()
{
ApplicationName = "app-name",
HttpClientInitializer = credential
});
// Do Stuff with the video here.
}}
The AuthYouTube() looks like this (the same controller):
public UserCredential AuthYouTube()
{
string filePath = Server.MapPath("~/Content/YT/client_secret.json");
UserCredential credential;
try{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows for full read/write access to the
// authenticated user's account.
new[] { YouTubeService.Scope.Youtube },
"username#domain.com",
CancellationToken.None,
new FileDataStore(Server.MapPath("~/Content/YT"),true)
).Result;
};
return credential;
}
catch(EvaluateException ex)
{
Console.WriteLine(ex.InnerException);
return null;
}
}
I have stored my client_secret.json that I downloaded from Google Developer Console inside the [project]/Content/YT. (Also tried inside the /App_Data folder.
When uploading the debugger is showing the folowwing message:
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ComponentModel.Win32Exception: Access is denied
Place where the error occures:
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
StackStrace:
[Win32Exception (0x80004005): Access is denied]
Microsoft.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +115
Microsoft.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccess(Task task) +78
Google.Apis.Auth.OAuth2.<AuthorizeAsync>d__1.MoveNext() in C:\Users\mdril\Documents\GitHub\google-api-dotnet-client\Src\GoogleApis.Auth.DotNet4\OAuth2\GoogleWebAuthorizationBroker.cs:59
[AggregateException: One or more errors occurred.]
System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) +4472256
Project.Controllers.VideosController.AuthYouTube() in d:\dev\Development\project\project\Controllers\VideosController.cs:133
project.Controllers.VideosController.Upload(HttpPostedFileBase uploadFile, Videos videos) in d:\dev\project\project\Controllers\VideosController.cs:71
What is the reason of this?
- Google API?
- folder / IIS rights?
Update 01-02-2016
Could it be some access error on the API side?
If not, could somebody please provide me the steps to grand the right IIS rights, still get the error after giving folder permissions.
Running the following code DOES create the folder as intended inside my App_Data, but also returns the same 'Access denied' error. The folder is empty.
var path = HttpContext.Current.Server.MapPath("~/App_Data/Drive.Api.Auth.Store");
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret }
, scopes
, userName
, CancellationToken.None
, new FileDataStore(path,true)).Result;
Could somebody please explain how to get this working?
After ready the documentation again I found a way to get access to the API and upload my videos to YouTube. I hope I can clarify the way i did this.
How i did this:
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
Create an callback controller:
using Google.Apis.Sample.MVC4;
namespace Google.Apis.Sample.MVC4.Controllers
{
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
}
Create class and fill-in the credentials:
using System;
using System.Web.Mvc;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Mvc;
using Google.Apis.YouTube.v3;
using Google.Apis.Util.Store;
namespace Google.Apis.Sample.MVC4
{
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "PUT_CLIENT_ID_HERE",
ClientSecret = "PUT_CLIENT_SECRET_HERE"
},
Scopes = new[] { YouTubeService.Scope.YoutubeUpload },
DataStore = new FileDataStore(HttpContext.Current.Server.MapPath("~/App_Data/clientsecret.json")),
});
public override string AuthCallback
{
get { return #"/AuthCallback/Upload"; }
}
public override string GetUserId(Controller controller)
{
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
var user = controller.Session["user"];
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
}
In my ActionResult I set the YoutubeService. the creating of my video take place inside my Upload POST
Your own controller (mine is for the /upload action):
public async Task<ActionResult> Upload(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var youtubeService = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "name",
});
return View();
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
For uploading logic see: https://developers.google.com/youtube/v3/code_samples/dotnet#upload_a_video
Set redirect URL in Google Developers console
In the Google Developers Console set the Authorized redirect URIs value to something like (my controller is called videos): http://www.domainname.com/Videos/Upload
**Using a single oAuth account **
Insted of saving the client id (GUID, see GetUserId inside AppFlowMetadata file) inside my session I now use one single id so I could use the same token/responsive for all the users.
I have an OWIN architecture in place with the following structure:
So first step the User login inside my MVC application using the following code:
var data = "grant_type=password&username=" + $scope.loginModel.Username + "&password=" + $scope.loginModel.Password;
$http({
method: 'POST',
url: identityApi + 'Token',
data: data,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).
success(function(data, status, headers, config) {
$scope.isLogged = true;
$window.sessionStorage.setItem("token", data.access_token);
}).
error(function (data, status, headers, config) {
$scope.isLogged = false;
$scope.queryError = data || 'An error occurred' + ' Status ' + status;
});
At this point I can call any [Authorize] Controller on my Web API and it succeed. The problem is inside my MVC application.
After I login, I cannot find in the cookies the authentication cookie generated by my custom OAuthAuthorizationServerProvider so inside my MVC application, when I check:
HttpContext.Current.User.Identity.IsAuthenticated
I always get back null while if I check this on my Data Controller (the one that receives the token id) I can extrapolate the Claim.
This is how I grant access by the /Token endpoint:
// verify the credentials
var userManager = new UserManager(new CustomUsersStorage());
CustomUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
// generate the Token identity
ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
// generate the Coockie identity
ClaimsIdentity cookiesIdentity = await userManager.CreateIdentityAsync(user, CookieAuthenticationDefaults.AuthenticationType);
// validate the token and the request
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
// signIn with the Cookie
context.Request.Context.Authentication.SignIn(new AuthenticationProperties() { IsPersistent = true }, cookiesIdentity);