I want to use this unmaintained project in order to authorize the services of my web api because it is mandatory for us to use Keycloak.
It has been quite easy to follow this tutorial and making it work in my web api but I cannot found any example of using tokens instead of interactive login.
The web api will receive with the http request an authorization header of our already logged client and the target is to securize the services and authorize them based on some roles.
I've perform some changes in Startup.cs - Configuration method:
ConfigureAuth(app);
app.UseKeycloakAuthentication(new KeycloakAuthenticationOptions
{
// App-Specific Settings
ClientId = "demo-app", // *Required*
ClientSecret = "ssshhhh", // If using public authentication, delete this line
VirtualDirectory = "", // Set this if you use a virtual directory when deploying to IIS
// Instance-Specific Settings
Realm = "PruebaRealm", // Don't change this unless told to do so
KeycloakUrl = "http://example.com/auth", // Enter your Keycloak URL here
// Template-Specific Settings
EnableWebApiMode = true,
AuthenticationType = "KeycloakOwinAuthenticationSample_keycloak_auth", // Unique identifier for the auth middleware
});
And I've added the authorization attribute to the htttp method:
[Authorize]
public IHttpActionResult Get()
{
PrivilegeProvider pPrivileges = new PrivilegeProvider();
var searchResults = pPrivileges.GetPrivilege();
if (searchResults == null)
return NotFound();
return Ok(searchResults);
}
When I perform the request including the authorization header
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: es-ES,es;q=0.9
Authorization: bearer thisisanexamplethisisanexamplethisisanexample....
Cache-Control: no-cache
Connection: keep-alive
Host: localhost:49729
Origin: http://localhost:4200
Pragma: no-cache
Referer: http://localhost:4200/privilegios
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36
I always receive:
Access Unauthorized: Requires valid bearer token authorization header
Can anyone give me an advice to achieve this goal?
I'm thinking about creating my own adapter using owin.
Thanks in advance.
Related
What I am trying to do
I've got a JS frontend that has webpack, and I'm routing the /api requests to a backend server like so:
proxy: {
'/api': {
target: 'https://[::1]:5001',
secure: false,
changeOrigin: true,
}
}
And a .NET 5 backend that has cors enabled for all origins and headers and methods.
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
This backend is trying to authenticate people with GitHub, as so.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "GitHub";
})
.AddCookie()
.AddOAuth("GitHub", options =>
{
options.ClientId = GitHubConfiguration.ClientId;
options.ClientSecret = GitHubConfiguration.ClientSecret;
options.CallbackPath = new PathString("/api/signin-github");
options.CorrelationCookie.SameSite = SameSiteMode.Lax;
options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
options.TokenEndpoint = "https://github.com/login/oauth/access_token";
options.UserInformationEndpoint = "https://api.github.com/user";
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
options.ClaimActions.MapJsonKey("urn:github:login", "login");
options.ClaimActions.MapJsonKey("urn:github:url", "html_url");
options.ClaimActions.MapJsonKey("urn:github:avatar", "avatar_url");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user.RootElement);
}
};
});
Pretty standard, from what I can tell.
To test it out I've got this endpoint
[ApiController]
[Route("api/[controller]/[action]")]
public class AccountController : ControllerBase
{
[HttpGet]
public IActionResult Login()
{
return Challenge(new AuthenticationProperties { RedirectUri = "/" });
}
This works pretty well when I call it directly in a browser at
https://localhost:5001/api/account/login
But when I try and git that endpoint from my frontend, I'm getting
Access to fetch at 'https://github.com/login/oauth/authorize?client_id=<CLIENT_ID>&scope=&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%3A5001%2Fapi%2Fsignin-github&state=<OAUTH_STATE>' (redirected from 'https://localhost:5001/api/account/login') from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Here's a basic summary of from fiddler:
GET https://localhost:5001/api/account/login
302 Redirect to https://github.com/login/oauth/authorize?etc
GET https://github.com/login/oauth/authorize?etc
302 Redirect to https://github.com/login?etc
There aren't any exceptions of logs in the server.
What seems to be happening is frontend -> backend is working fine, but then the backend is trying to redirect the frontend which isn't allowed due to cross origin limitations.
Am I correct in thinking that I need to somehow get the redirect to hit the server instead of what it still thinks is the origin (as the webpack dev server proxy is keeping the origin as localhost:8081 even after the request is proxies)? If so, how do I then respond to the frontend?
I've been round in circles for a couple of hours with this, so any push in the right direction would be great.
Cheers,
EDIT Here's a closer look at the headers coming back from that last redirect that's causing it:
GET https://github.com/login/oauth/authorize?client_id=<oauthinfo>
Host: github.com
Connection: keep-alive
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36
Accept: */*
Origin: null
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://localhost:8081/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
HTTP/1.1 302 Found
Server: GitHub.com
Date: Mon, 26 Apr 2021 21:27:40 GMT
Content-Type: text/html; charset=utf-8
Vary: X-PJAX
permissions-policy: interest-cohort=()
Location: https://github.com/login?client_id=<oauthinfo>
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Frame-Options: deny
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
Expect-CT: max-age=2592000, report-uri="https://api.github.com/_private/browser/errors"
Content-Security-Policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; connect-src 'self'
Vary: Accept-Encoding, Accept, X-Requested-With
X-GitHub-Request-Id: D17E:70F9:663C530:5A21FA1:7097304C
Content-Length: 545
It seem your application is being redirect(302) back and forth from http=>https and again its going in same loop. Make the https request and enable https for your local end point. It should work then
I didn't realise that you can't do an authentication flow using a regular fetch GET.
The configuration was correct, and using
window.location.href('https://localhost:5001/api/account/login')
in the JS or using a link such as
<a href='https://localhost:5001/api/account/login'>
Login to GitHub
</a>
Fixed my issue.
The reason is simple, and one I overlooked. You need to make the request to the server directly, which then allows you to go through the whole authentication flow independent of the front-end, allowing you to forego any keys in the front-end that could be less secure.
At the end of the flow, you're redirected back to the front-end, fully authenticated.
I'm looking to utilise an XSRF token with my .NET Core 2 API.
I'm using a Vue.Js front-end.
I have the back-end configured correctly as instructed here:
https://stackoverflow.com/a/44035774/2027404
Once logging in, I create a cookie called "XSRF-TOKEN" and I've written an interceptor in Vue to mimic what AngularJS does, it looks for the cookie and appends the XSRF token with header "X-XSRF-TOKEN" to the controller route that is protected by an [AutoValidateAntiforgeryToken]
attribute.
Below is an example trace of a Post request that is made after the cookie is persisted and login is completed:
POST /api/auth/Test HTTP/1.1
Host: localhost:5000
Connection: keep-alive
Content-Length: 0
Origin: http://localhost:5000
X-XSRF-TOKEN: CfDJ8Ku5qKiYnPBCmHLFQRR3pGmv482utwpDs1AssvvtQ1yf2eGgasNE4DOTdxivPLqv5e4TGuG800elYMFyJqTC7bzOfnY0HVCUD-Dw0pn-bkBZeN2GjBBvqMJ79Vcwun4khLe9qlzxyBTB0W1XEu_OJq0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36
Content-Type: application/json;charset=utf-8
Accept: application/json, text/plain, */*
X-Requested-With: XMLHttpRequest
Referer: http://localhost:5000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: XSRF-TOKEN=CfDJ8Ku5qKiYnPBCmHLFQRR3pGmv482utwpDs1AssvvtQ1yf2eGgasNE4DOTdxivPLqv5e4TGuG800elYMFyJqTC7bzOfnY0HVCUD-Dw0pn-bkBZeN2GjBBvqMJ79Vcwun4khLe9qlzxyBTB0W1XEu_OJq0
So the point really is to prove that not only is my cookie sent but I am including the header which is expected by my Startup.cs.
public void ConfigureServices(IServiceCollection services) {
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = Configuration["JWT:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["JWT:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:SecretKey"])),
ValidateLifetime = true
};
});
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
services.AddMvc();
}
My controller method is then as follows:
[HttpPost ("[action]")]
//[Authorize]
[AutoValidateAntiforgeryToken]
public IActionResult Test () {
return Ok ("Working fine!");
}]
I'm definitely not doing anything stupid like pointing at the wrong route. If I disable AVAT and use Authorize instead with JWT (I'm aiming to eventually utilise both JWT & XSRF together to go with the double cookie method for security), I can get a 200 response providing I provide a valid Authentication Bearer token in my request header...
Any ideas as to why this out of the bag functionality isn't playing ball? Always returning 400...
I could show more source code but the proof is in the pudding within the request trace... all the data that should be required, is there.
Thanks
Problem solved.
Ensure to use the IAntiforgery.GetAndStoreTokens(HttpContext) Method to not only give you your XSRF token but also most importantly store the AspNetCore.Antiforgery token that .NET Core expects by default using this form of authentication.
I was using IAntiforgery.GetTokens(HttpContext) only as in the linked stack overflow post above and while it was allowing me to fetch my XSRF token and append it to my header, I was missing the antiforgery cookie.
I am converting an old ASP.NET web forms site to ASP.NET MVC 5. I would like to issue permanent redirects for the old page URLs.
Here is what I have done -
RouteConfig.cs:
routes.MapRoute("About_old",
"About/About.aspx",
new { controller = "Home", action = "About_old" });
HomeController.cs:
public ActionResult About_old()
{
return new RedirectResult("/About", true);
// I've also tried
// return RedirectToActionPermanent("About");
// return RedirectPermanent("/About");
}
All attempts load the correct /About view, however the URL does not change, and I do not see a 301 code in the response. In other words, the URL is "localhost/About/About.aspx" and I expect it to be "localhost/About"
Complete Request/Repsonse from Chrome:
Request URL:http://localhost:55774/About/About.aspx
Request Method:GET
Status Code:200 OK
Request Headers
GET /About/About.aspx HTTP/1.1
Host: localhost:55774
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Response Headers
Cache-Control:private
Content-Encoding:gzip
Content-Length:2284
Content-Type:text/html; charset=utf-8
Date:Sat, 01 Mar 2014 18:10:41 GMT
Server:Microsoft-IIS/8.0
Vary:Accept-Encoding
X-AspNet-Version:4.0.30319
X-AspNetMvc-Version:5.1
X-Powered-By:ASP.NET
Nowhere do I see a 301 and the URL does not change. I have to think this has to do with how I am mapping the route of the old aspx page in RouteConfig.cs as all action methods have the same results. NOTE I have put a solution using global.asax below, however I would prefer it to work as I am attempting above, so I have not accepted my answer.
Am I doing something wrong or just missing something? How do I get the 301 to issue and URL to change?
Here is my solution (Global.asax)
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
string currentUrl = HttpContext.Current.Request.Path.ToLower();
if (currentUrl.EndsWith("/about/about.aspx"))
{
Response.Status = "301 Moved Permanently";
Response.AddHeader("Location", "/About");
Response.End();
}
}
From answer here: Global 301 redirection from domain to www.domain
This is a follow-up question to the suggestion of user #Kiewic to make a post request using a webview: Post data with a request in a Windows Store app WebView - using C#
So I am using this code
// defined before: string uri, User user
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri));
request.Content = new HttpStringContent(
String.Format(
"language={0}&session_id={1}&user_id={2}",
Text.GetLanguage(),
user.session_id,
user.user_id.ToString()
),
Windows.Storage.Streams.UnicodeEncoding.Utf8,
"application/x-www-form-urlencoded"
);
webView.NavigateWithHttpRequestMessage(request); // webView defined in xaml
Fiddler shows me this request:
POST http://mobile.mysite.com/ HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US,en;q=0.8,de-CH;q=0.5,de;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; SMJB; WebView/2.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: mobile.mysyte.com
Content-Length: 101
Connection: Keep-Alive
Pragma: no-cache
Cookie: _ga=GA1.2.769009833.1387593479; PHPSESSID=nk6b04rb7d7vu4vmm2ish7l0o4
language=en&session_id=fhihferihguiegierewfrefghxrfer&user_id=1
The webserver technology is PHP, so I used this code to print the post content
<?php
var_dump($_POST);
?>
But the result is an empty array. So where is my mistake?
According to a Microsoft employee there is a bug with setting the headers of the request content. This should mean that nothing is wrong with my code in the first post:
http://social.msdn.microsoft.com/Forums/windowsapps/en-US/7deaf4ba-611f-4957-af3c-9b2b2e1e1b8b/webviewnavigatewithhttprequestmessage-set-contenttype?forum=winappswithcsharp
This means you cannot really use NavigateWithHttpRequest and you should use another approach to post data. User #Kiewic made a suggestion which works on Windows 8 too:
Post data with a request in a Windows Store app WebView - using C#
I have a local website (not mine) that requires authentication before doing some queries. The authentication header looks like this:
Host: 192.168.7.9
Connection: keep-alive
Content-Length: 185
Origin: http://192.168.7.9
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/27.0.1453.3 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
DNT: 1
Referer: http://192.168.7.9/signin
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: _FProEnterprise_session=BAh7CzoVbmVlZF93ZWxjb21lX21zZ1Q6D3Nlc3Npb25faWQiJTUxNjI5OGRiMDNmNjU4ZDg4ODE3NmFiZjhmMDU3YTI2OglzaXRlSSIKc2l0ZTAGOgZFRjoObGFuZ19wYXRoSSIHZW4GOwhUOg5vbmVfY2xpY2tGOgx1c2VyX2lkaRE%3D--0c6634a714baa7f0e4795aee89b31f9b7ec0565e
And the request body looks like this:
username=myusername&password=mypassword
I'm not super great with how authentication works. So first, is this forms authentication? I'm guessing it is, since I have to enter my username and password on the site then submit to get in.
Second, why is there a Cookie already there? Is it from a previous session perhaps, and I can ignore it?
My goal is to reproduce this in C#, so that I can authenticate, get the cookie and then post data and retrieve results from this site. At least thats what I think I need to do. Links and code would be super helpful. If it's helpful I need to make this request from my web.api app controller.
You use asp.net membership provider and do the authentication like Membership.ValidateUser() and that will authenticate the formsauthentication also. Check if it is authenticated if (Context.User.Identity.IsAuthenticated) - FormsAuthentication.SignOut();
You need sql server or some kind of authentication mechanism first to save the username and password.
This seems to be an AJAX request (X-Requested-With: XMLHttpRequest). Therefore the user has to be on the web page first, which is when the session started. That is when the user gets the session cookie, which is sent every time to keep track of the session. This session is also kept on the server, where login information is stored - whether or not you're logged in, and who you are.
The contents seem to be a simple HTTP form, but since it came from an XMLHttpRequest it could just as well be created using Javascript. This is at least the standard way to send POST data through HTTP.
That is using plain HTTP authentication and the cookies are from an old session.
http://en.wikipedia.org/wiki/Basic_access_authentication
This link solved it for me:
HERE
My final code (in my web.api controller looked like this):
public static string JsonWithAuth( string url, string data )
{
var bytes = Encoding.Default.GetBytes( data );
using ( var client = new WebClientEx() )
{
var values = new NameValueCollection
{
{ "username", "myUsername" },
{ "password", "myPassword" },
};
// Authenticate
client.UploadValues( "http://192.168.7.9/main/signin", values );
// Post data
var response = client.UploadData( url, "POST", bytes );
return Encoding.Default.GetString( response );
}
}
And this was the class that made it work (from the linked answer):
/// <summary>
/// A custom WebClient featuring a cookie container
/// </summary>
public class WebClientEx : WebClient
{
public CookieContainer CookieContainer { get; private set; }
public WebClientEx()
{
CookieContainer = new CookieContainer();
}
protected override WebRequest GetWebRequest( Uri address )
{
var request = base.GetWebRequest( address );
if ( request is HttpWebRequest )
{
( request as HttpWebRequest ).CookieContainer = CookieContainer;
}
return request;
}
}
So my final call was like this:
string sampleInfo = JsonWithAuth(
"http://192.168.7.9/samples/sample_locations_list",
"sort=position&dir=ASC&box_id=");
Hope that helps someone else!