Using WebApi to authenticate with ADFS - c#

We are trying to create following scenario:
Html page sends ajax request to WebApi asking wheter user has certain user role or not. WebApi checks from ADFS if user is logged in (if not, WebApi authenticates user). WebApi then reads user roles from ADFS and returns true/false to html page.
What we have so far:
Ajax sends Get-request to WebApi. In WebApi we have Authorize-tag, which correctly sends user to ADFS-authentication. However, after authentication ADFS returns html-page containing saml information to client instead of WebApi. So now we create another ajax request (post this time) which has received html-page as data. WebApi then parses this and returns either true/false based on user roles in SAML-response.
Questions:
Currently used mechanism seems clunky. Is this mechanism correct or is there a better way to do this?
If we use method above, is there a possibility that user edits received html-page and gives himself roles that he doesn't really have?
Even if above mechanism is correct, we are still having cors-error when WebApi redirects to authentication. Cors is enabled in WebApi's Startup.cs. How do we get rid of this?
Codes:
Ajax:
var uri = 'api/user';
$(document).ready(function () {
$.ajax({
url: uri,
type: "Get",
success: function (data) {
$.ajax({
url: uri,
type: "POST",
data: data,
success: function (value) {
if (value == true) {
$('#userData').text('You have correct role.');
}
else {
$('#userData').text('You don't have correct role.');
}
},
error: function (jqXHR, textStatus, err) {
window.location.href = "NotLoggedIn.html";
}
})
},
error: function (jqXHR, textStatus, err) {
window.location.href = "NotLoggedIn.html";
}
});
});
Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
MetadataAddress = ConfigurationManager.AppSettings["ida:AdfsMetadataEndpoint"],
Wtrealm = ConfigurationManager.AppSettings["ida:Audience"],
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
}
UserController:
namespace SSOWebApiSample.Controllers{
[RoutePrefix("api/user")]
public class UserController : ApiController
{
[HttpGet]
[Route("")]
[Authorize]
public IHttpActionResult GetUser()
{
return Ok();
}
[HttpPost]
[Route("")]
public async Task<IHttpActionResult> PostUser()
{
bool isAdditionalInfoAllowedUser = false;
string result = await Request.Content.ReadAsStringAsync();
//Parse result here
return Ok(isAdditionalInfoAllowedUser);
}
}
}

AdalJS will clean this up nicely. Please refer to the following:
To Do application adapted from Azure AD to ADFS
Azure AD sample with CORS
Enabling Cross-Origin Requests in ASP.NET Web API 2
To make successful CORS Web API calls with ADFS authentication, I found needed to set instance, tenant, clientId, and endpoints members when calling adalAuthenticationService.init() in my Angular app's Config. See sample two for endpoints example, but replace GUID with URL.

Related

Antiforgery token validation in header using Ajax and MVC

I need to implement antiforgery validation to my MVC project. However, I am having problems with the server side.
I am using #Html.AntiForgeryToken() to create the token.
Then i get the token value and include it in Ajax request.
// Get token
var cookie = $('input[name=__RequestVerificationToken]').val();
// Ajax request
return $.ajax({
url: url,
type: 'post',
headers: {
'x-system-source': headerValue,
'x-verification-token': cookie //verification token
},
data: ko.toJSON(entity),
contentType: 'application/json',
dataType: 'json'
});
Code below comes from microsoft documentation, however, I am not sure how to implement it. Should I create a custom attribute with the method below and add the attribute to every http request? If so, what would be the best way? Is it possible to create attribute for a controller level? I would also like to avoid repetitive code as much as possible. In addition, is there a way to unit test aniforgery validation feature to make sure that it works as expected.
I am new to the web development, and I would appreciate if you could provide code samples or point me in the right direction. Thanks for your time.
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("x-verification-token", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}

Session and Token With Each Request in ASP.NET MVC

I developed a project using ASP.NET MVC that uses session to keep track of users after login. Simply authorization! So I used the below code to use it as attribute in required controllers:
public class GppAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["userId"] == null)
return false;
else
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectResult("~/Auth/Login");
}
}
Finally in controller, doing this:
[GppAuthorize]
// GET: Dashboard
public ActionResult Index()
{
return View();
}
So for above, the scenario works fine. Now I am trying to validate each request with a token for authentication (Checks if the request has valid token to work with server-side) and not sure how to do that in ASP.NET MVC 5 as most of the tutorials uses Web Api. I did few R & D and got this for a basic idea to start. Here is the link with an answer:
Authenticate MVC Controller Using Bearer Token and Redirect To The Controller
It looks promising, the questions are: After login,
How can I create the token and pass it in each http request specifically after user login?
Is there anything that I require to do with session or it should be independent of session
anyway?
If the example code with provided link works, how can I make it work for http request with
Ajax call? Say for below code sample:
$.ajax({
type: "POST",
url: "/Dashboard/GetProducts",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (value) {
alert(value);
GetProjectDetails();
},
error: function (ex) {
alert('Failed to retrieve states.' + ex); //Check if authentication failed here
}
});
Will this help me to prevent unauthorized url to access data from the website or web project
that I am working with (Though I know, it'll but is there any way to override and make
unauthorized url calls)?

Authorization has been denied for this request even when user is authenticated

Suddenly, this error started happening. The web api method is this:
// POST api/Account/Logout
[Route("Logout")]
public IHttpActionResult Logout()
{
Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return Ok();
}
That method is inside AccountController which has [Authorize] attribute.
When I just logged in and I see a page that also has [Authorize], I press a button that allows me to log out. Tha log out button calls the Web Api using Ajax.
Let me tell you also that this worked before.... suddenly it stopped working.
I have added this code in Startup.Auth.cs:
var config = new System.Web.Http.HttpConfiguration();
app.UseWebApi(config);
But it did not work either. I added that line just in case, because as I have old you, this worked before, without that line.
Any help on this, please?
Also please, don't suggest to remove [Authorize] attibute, as I have seen in other answeres here. Web Api's have to be called being authenticated.
EDIT: this is the call in Ajax:
self.logout = function () {
// Log out from the cookie based logon.
var token = sessionStorage.getItem(tokenKey);
var headers = {};
if (token) {
headers.Authorization = 'Bearer ' + token;
}
$.ajax({
type: 'POST',
url: '/api/Account/Logout',
headers: headers
}).done(function (data) {
// Successfully logged out. Delete the token.
self.user('');
sessionStorage.removeItem(tokenKey);
location.href = '/';
}).fail(showError);
}

ASP.NET Core 2.0 Web API throwing 500 error and not allowing access on http post

I've set up my Cors policy in my Startup.cs by first adding the policy to ConfigureServices method:
services.AddCors(
cfg =>
{
cfg.AddPolicy("MyPolicy",
bldr => {
bldr.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("http://localhost:8001"); });
cfg.AddPolicy("AnyGET",
bldr => { bldr.AllowAnyHeader().WithMethods("GET").AllowAnyOrigin();});
});
And next I'm using this policy as declared in my Configure method:
app.UseCors("MyPolicy");
app.UseAuthentication();
app.UseMvc(config => { config.MapRoute("MyAPIRoute",
"api/{controller}/{action}"); });
In My Controller I've Enabled Cors specifying my policy and I've configured a specific Controller method to accept a bearer token as as way authenticating a the http post:
namespace My.Api.Controllers
{
[EnableCors("MyPolicy")]
public class AccountController : Controller
{
....
[HttpPost]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<IActionResult> ChangePassword([FromBody] PasswordChangeViewModel model)
{
From my angular app I am making a http post request to this method:
public changePassword(password: Password) {
return this.http.post("http://localhost:8000/api/account/changepassword", password,
{
headers: new Headers({"Authorization": "Bearer " + localStorage.getItem('token').toString()})
}).pipe(map((res: Response) => res.json()));
}
I've tried every suggestion on stack overflow but nothing seems to work and continue to get this error:
I have the same setup for another project and this wasn't ever an issue. The only difference is I updated this angular app to the angular 6 from 5. Also whenever I make this call from Postman its successful. I've hit a wall and need help! Thanks in advance!
UPDATE
I've changed the angular call to:
public changePassword(password: Password) {
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + localStorage.getItem('token').toString() });
let options = new RequestOptions({ headers: headers });
return this.http.post("http://localhost:8000/api/account/changepassword", password, options);
}
I'm still getting the same error.
UPDATE
I have also tried to change how my angular service makes the http post by using HttpClient from #angular/common/http:
public changePassword(password: Password) {
let headers = new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + localStorage.getItem('token').toString() });
return this.httpClient.post("http://localhost:8000/api/account/changepassword", password, { headers: headers} );
}
UPDATE
I'm going in circles with this. It seems like I've tried EVERYTHING. Please help!!
I finally figured this out yesterday smh...I'm ashamed but the issue was that one of my Password model properties didn't match the ModelView Password in my API. Once they matched this worked fine...

Get request origin in C# api controller

Is there a way how can I can get request origin value in the api controller when I'm calling some api endpoint with ajax call?
For example I'm making this call from www.xyz.com:
$http({
url: 'http://myazurewebsite.azurewebsites.net/api/ValueCall/CheckForExistingValuers',
method: "GET",
params: { loanID: $scope.loanIdPopup }
}).success(function (data) {
}).error(function (data) {
});
Once on the api side, how can I get the www.xyz.com value?
CORS is working properly.
What you're looking for is probably the origin-header. All modern browsers send it along if you're doing a cross domain request.
In an ApiController you fetch it like so:
if (Request.Headers.Contains("Origin"))
{
var values = Request.Headers.GetValues("Origin");
// Do stuff with the values... probably .FirstOrDefault()
}
You can grab it from the API methods via current HTTP request headers collection:
IEnumerable<string> originValues;
Request.Headers.TryGetValue("Origin", out originValues)
var originValue = Request.Headers["Origin"].FirstOrDefault();
// or
StringValues originValues;
Request.Headers.TryGetValue("Origin", out originValues);

Categories