IdentityServer v3 and custom cookies - c#

I'm updating our MVC application to use IdentityServer v3 to implement OpenID Connect authentication using implicit flow.
I've overridden the IdentityServer AuthenticateLocalAsync method of the UserServiceBase class to call into our existing authentication service to validate the supplied credentials. This service also sets various cookies required by our application.
The issue I'm encountering is that upon entering the credentials in the login page, it doesn't redirect to the ReturnUri specified in the OpenIdConnectAuthenticationOptions parameter of UseOpenIdConnectAuthentication method. Instead it remains on the login page and appears to go into an infinite loop, alternating between calls to the notification handlers SecurityTokenValidated and SecurityTokenReceived. There is no indication of any error in the IdentityServer logs.
I think the cause of the issue is related to setting the application-specific cookies in our authentication service. If I comment out the following, it then successfully redirects upon logging in:
var cookie = new HttpCookie("AppCookie") { HttpOnly = false, Secure = true };
cookie ["CustomField1"] = HttpUtility.UrlEncode(customValue1);
cookie ["CustomField2"] = HttpUtility.UrlEncode(customValue2);
this._httpContext.Response.Cookies.Set(cookie);
However these cookies are required by our application and so need to be set in the response. Why would setting these cookies prevent it from redirecting? Are there any workarounds? Any assistance would be much appreciated.

Related

Handling authentication without a cookie

Our team is using Itfoxtec as the saml2 handler in our SP as follows:
A client clicks on the link of the login API.
The API redirects the user to the IdP login page.
On successful login, The API gets a SAML2 response to the ASC route.
We fetch the claims from the response.
If the user is good to go, we generate a JWT token to be used in next requests to other services using the Authorization header, otherwise we send unauthorized response.
Here is the configuration of the handler:
builder.Services
.AddAuthentication("saml2")
.AddCookie("saml2", cookieAuthenticationOptions =>
{
cookieAuthenticationOptions.SlidingExpiration = true;
cookieAuthenticationOptions.LoginPath = new PathString("/saml/request");
cookieAuthenticationOptions.Cookie.SameSite = SameSiteMode.None;
cookieAuthenticationOptions.Cookie.SecurePolicy = CookieSecurePolicy.Always;
Task UnAuthorizedResponse(RedirectContext<CookieAuthenticationOptions> context) =>
Task.FromResult(context.Response.StatusCode = (int)HttpStatusCode.Unauthorized);
cookieAuthenticationOptions.Events.OnRedirectToAccessDenied = UnAuthorizedResponse;
cookieAuthenticationOptions.Events.OnRedirectToLogin = UnAuthorizedResponse;
});
As we can see that the use of Itfoxtec is done in the initial login process only, where the user either gets unauthorized response, or gets JWT token and no additional calls to the SP is done.
Note that when I remove the Cookie related configurations, I get an exception thrown while fetching the claims and creating a session on the following line:
customClaims = await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: GetCustomClaimsPrincipal);
Our question is, is there any way else to use the Itfoxtec as a SAML2 handler but without using Cookies?
You do not need to use session cookies. If you do not use session cookies you should not call the CreateSession method which create the cookie based .NET session.
The SAML 2.0 Authn request is validated in the Unbind method and you can thereafter safely read the users claims in saml2AuthnResponse.ClaimsIdentity.Claims.
FoxIDs use the ITfoxtec Identity SAML 2.0 component without using the cookie based .NET session, you can see how the SAML 2.0 Authn request is validated in the AuthnResponseAsync method.

How to authenticate an HttpClient connection with an external provider (Google)

Edit:
Here is my question reformulated:
I have a web server with secured api endpoints - one must have been authenticated with Google prior to using them. I implemented Challenge and Callback endpoints for that.
This works well from a browser with my SPA web front-end. The user gets redirected to the Google website to sign-in and then gets redirected back to my webapp; the browser then has the authenticated cookies and the webapp can use the endpoints to update its state.
I also have a WPF application that will communicate with the web server.
I want the WPF application to do the same as the web front-end: Use the web api endpoints after being authenticated with Google. The connection between the WPF application and my web server is done through an HttpClient.
My problem is I don't know how to authenticate that HttpClient connection between the WPF app and the web server.
I tried using the same Challenge endpoint but the response I get is of course the HTML from the Google Sign-In page, so I guess I can't use that with an HttpClient...
I also tried authenticating with GoogleApis from the WPF app and use the authenticated token to set cookies in the HttpClient but apparently this is not compatible.
How to authenticate an HttpClient connection to a web api with an external provider such as Google?
Original question:
From a WPF application, the user authenticates with Google with this code:
using Google.Apis.Auth.OAuth2;
...
public void Authenticate()
{
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "myClientId",
ClientSecret = "myClientSecret"
},
new[] { "email", "openid" },
"user",
CancellationToken.None).Result;
}
This works and the UserCredential object contains the authenticated token:
How to embed this token information in a web request made with an HttpClient in order to call my webapi endpoint?
I think the request must include some cookies to inform the server that it has been authenticated, but I don't know which ones exactly.
The endpoint on the server-side validates that the user is authenticated with the help of IdentityServer:
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
If I got your question right, you just have to set the Authorization header
var credentials = await GoogleWebAuthorizationBroker.AuthorizeAsync(
clientSecrets,
new[] { "email", "openid" },
"user",
CancellationToken.None);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
credentials.Token.TokenType,
credentials.Token.IdToken);
Maybe you'll find below a helpful hint to better understand OpenID :)
The confusion stems from mixing GoogleApis and IdentityServer frameworks.
Authentication/authorization can be achieved using either of them.
Objects from Google.Apis.Auth.OAuth2 and IdentityServer4 namespaces are not designed to interact.
No manual cookie handling is necessary, for sure.
Ask yourself to whom does Google provide trust for the user. If it calls back to WPF, then webapi trusting WPF is a separate issue.
You answer your own question in the question:
the browser then has the authenticated cookies and the webapp can use
the endpoints to update its state
HttpClient needs to send those same cookies.
How do I set a cookie on HttpClient's HttpRequestMessage
If I understood your question right, then I faced the same problem not too long ago.
The way I implemented it is that in the backend, no matter who tries to access the endpoint, they had to send a Bearer X authorization token.
The token contained the identity of the client that wanted to access the resource, and I checked if he was permitted.
No matter what kind of client wants to access the endpoint, it just has to have that authroziation header in the request that he sends and the backend will treat it the same.
In my scenario, I used an authentication service that returns a cookie to the client with a certain JWT that contains the identity information.
Then from the client I send in every request the JWT received from the authentication service as an authorization header to the backend.
The reason I had to put the JWT that I receive from the service in a header, is that the authentication service and the backend service are not in the same domain, so cookies cant be shared.
This results in such design that no matter how you authenticate the client, the end result must be some sort of token that the backend can receive and read.
Hope this helps.

Antiforgery cookie in ASP.NET Core 2.0

I've been trying to understand exactly how the antiforgery works. What confuses me are the cookies which are created. To my understanding, you include the antiforgery token in your form and then you validate for that token when a request is made. This way if a third party websites posts to your website, it will be denied.
Now, I'm reading here https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-2.1 that the antiforgery token gets stored in a cookie, maybe I'm reading this wrong? But why? Isn't the whole point of this not to make this value accessible outside of your website? If I look at my cookies, I can see 3 cookies created with antiforgery in their name.
services.AddAntiforgery(options =>
{
options.CookieDomain = "contoso.com";
options.CookieName = "X-CSRF-TOKEN-COOKIENAME";
options.CookiePath = "Path";
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.RequireSsl = false;
options.SuppressXFrameOptionsHeader = false;
});
I did a little test, I created a post form which ended up including anti forgery token and then I tried submitting it and it worked. Then I created another form without the token and then it failed. So to me it seems it only looks for the token passed in the form, then what is the cookie for?
Anti-forgery is a two-part process. When the page is generated, the token is included as part of the form, so that it will be posted along with the rest of your data. The cookie is set, for the client side of things. When the post is made, the client sends the request with the post data (including the token) and it sends the cookie back to the server, which also includes the token. Server-side, the posted token is matched up with the cookie token, and rejected if the two don't match.
This may seem weird since the client is posting both, but the cookie part ensures that the same client that got the page is also the same client sending it back. The goal isn't so much to protect the anti-forgery token, but rather to ensure that the page on your site is the one that's submitted, rather than some scammer's recreated version of your page. Since a third-party would be incapable of setting a cookie for your domain, there's no way they can fake this portion of the check, even if they were able to retrieve a valid token from your page by requesting it and parsing out the token.
From the asp.net core website AntiforgeryOptions.Cookie Property. The cookie part of the CSRF is only necessary when using cookie based authentication.
The cookie used by the antiforgery system is part of a security system that is necessary when using cookie-based authentication.

How do I make the AuthorizeEndpointPath work in ASP.NET Oauth 2.0 framework

I currently have a website where I am trying to implement the OAuth server framework. The website is currently a combination of Web Forms (not MVC) and Web API 2. For the purposes of what I am trying to do, we cannot change the overall architecture of the system.
So far, I have OAuth working via the Web API for validating clients, generating access tokens, and handling refresh tokens.
The last piece I am trying to implement is the Authorize Code workflow. This will allow us to grant access to our application so integration tools can access our APIs without having to locally store the user credentials.
My understanding of how it should function is the following:
User is directed to the AuthorizeEndpointPath that I define in the Startup.Auth file
User either sees a login page (if they don't already have an authentication cookie for the website), or they see a special page where they have to grant permission to access the account (similar to what Google has)
Once the user clicks the "grant" button, the OWIN middleware will process the request, and redirect them back to the original client URL that requested access
However, after all of my configuration, whenever I access the AuthorizeEndpointPath directly, the "grant access permission" page is never actually displayed.
I've ensured the following configuration, but there isn't much documentation on what the correct configuration is.
var oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/token"),
AuthorizeEndpointPath = new PathString("/LoginAuthorize.aspx"),
//AuthorizeEndpointPath = new PathString("/api/authorize"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(10),
Provider = new ApiAuthorizationServerProvider(),
RefreshTokenProvider = new ApiRefreshTokenProvider(),
AuthorizationCodeProvider = new ApiAuthoirzationCodeProvider()
};
Currently the "AuthorizeEndpointPath" property maps to an actual page, where I ask the user confirmation, but that page is not being displayed at all
Through debugging, I can see the framework hits the following method even before the authorization page would be loaded
ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
I have tried to overload that method, and one of 2 things happens. If I make a call to context.Validated();, the user is immediately redirected without the authorization page being displayed. If don't "validate" the redirect URI, then a blank page is displayed indicating an "invalid_request".
So the big question is how do I actually make OWIN display my custom authorization page?
When the authorization page is finally displayed, what would I need to do when the user clicks on the "grant" button. Is there any configuration I need to setup, or any calls to OWIN I need to make?
Also, how do I ensure the user authenticates before that page is displayed? Do I simply have my code redirect the user to the login page if they are not logged in? If so, how will OWIN handle the real redirect back to the client (if the user would be redirected to the authorization page once they login)?
Finally, once this is all configured properly, will I still be able to support the current OAuth workflow I have of allowing clients to manually pass in their credentials for the "token" API? The reason I ask is because we also have mobile apps that have their own sign-in screen, and will be using OAuth to connect (in addition to other web-based clients).
I had a question that turned out to be similar to yours.
So, after quite some searching online, I got some success by searching github. Apparently, OAuthAuthorizationServerProvider offers AuthorizeEndpoint and that method should be used for both "Hey, you're not authorized, go log in you!" as well as for "Ahh, okay you're cool, here's an authorization code.". I had expected that OAuthAuthorizationServerProvider would have two separate methods for that, but it doesn't. That explains why on github, I find some projects that implement AuthorizeEndpoint in a rather peculiar way. I've adopted this. Here's an example:
public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
if (context.Request.User != null && context.Request.User.Identity.IsAuthenticated)
{
var redirectUri = context.Request.Query["redirect_uri"];
var clientId = context.Request.Query["client_id"];
var authorizeCodeContext = new AuthenticationTokenCreateContext(
context.OwinContext,
context.Options.AuthorizationCodeFormat,
new AuthenticationTicket(
(ClaimsIdentity)context.Request.User.Identity,
new AuthenticationProperties(new Dictionary<string, string>
{
{"client_id", clientId},
{"redirect_uri", redirectUri}
})
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan)
}));
await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);
context.Response.Redirect(redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token));
}
else
{
context.Response.Redirect("/account/login?returnUrl=" + Uri.EscapeDataString(context.Request.Uri.ToString()));
}
context.RequestCompleted();
}
Source: https://github.com/wj60387/WebApiOAUthBase/blob/master/OwinWebApiBase/WebApiOwinBase/Providers/OAuthServerProvider.cs
You create a separate login page at /account/login. What this does is sign the user in. If your WebAPI uses cookie-based authentication, you can just redirect the user back to the AuthorizeEndpoint again. If you use access tokens, your login page has to make a request to `AuthorizeEndpoint' with the access token to obtain an authorization code. (Don't give the access token to the third party. Your login page requests the authorization code and sends that back.) In other words, if you use access tokens then there are two clients involved in this flow.

Where does the application cookie go when created?

I'm playing around with the authentication using Owin according to a blog and when I execute the following, I'm supposed to get an (application) cookie. I'm unsure how to verify that I actually got one. So it very well might be there, somewhere in the memory or on the disk but I'm too ignorant of security issues to determine that.
List<Claim> claims = new List<Claim> { new Claim("Donkey", "Hazaa") };
ClaimsIdentity identity = new ClaimsIdentity(
claims, DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties
{
AllowRefresh = false,
IsPersistent = false,
ExpiresUtc = DateTime.Now.AddMinutes(1)
});
I can't verify by being able/unable to log in, since this is just a dummy without any backbone yet. The only thing I need to do at this stage is to confirm that something gets somewhere and that it vanishes when a minute has passed. It'd be awesomely awesome if there was an actual cookie file popping up somewhere and then poofing away after a while. Is there such?
Of course it is, but the Cookie is sent to the browser once the authentication was requested and validated. The cookie has the same behavior as in previous version of cookie auth with forms authentication, so is a task for the browser to receive and manage the cookie; on each HTTP request, the browser sends its cookies, which are processed by OWIN to check authentication.
Take the code and implement a simple authorization on memory (like user: abc, pass: 123) and check how the browser receives the cookie. By the way, the only difference between cookie and token validation is that the cookie is automatically sent on every request, whilst on token auth you need to explicitly send the token information on header.
Be aware that cookie authentication is intended for browsers, as mobile applications or other applications that need to connect to your API will not have access to the cookie response, like in a desktop app.
Regards

Categories

Resources