I am trying to implement authentication using jQuery and Forms Authentication on an ASP.NET MVC 3 web service. The idea is that I'll do a jQuery AJAX post to the web service, which will do Forms Authentication and return a cookie or token, and with each data access call, my web application (jQuery) will use that cookie or token.
What I have -
I have the AJAX call to the ASP.NET Web service set up, and I have the web service set up as follows:
public ActionResult Login(string userName, string password, bool rememberMe, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(userName, password))
{
return Json(FormsAuthentication.GetAuthCookie(userName, rememberMe), JsonRequestBehavior.AllowGet);
}
else
{
return Json("Authentication Failed", JsonRequestBehavior.AllowGet);
}
}
}
This is working so that if I make the AJAX call with correct credentials, I am returned a cookie in JSON, and if not, I'm returned the auth failed string.
What I don't know - What to do with the JSON cookie once I receive it back. I can store this in HTML5 local storage, but I don't know what part of it (or the whole thing) to send back with my data access calls, and how to interpret it and check the cookie on the web service side. If I shouldn't be using cookies, is there a way to generate and use a token of some kind?
For anyone else who happens to come across, here's how I solved it:
I realized that sending the cookie back via JSON was not necessary. By using FormsAuthentication.SetAuthCookie, an HTTP Only cookie is set, which gets automatically sent with AJAX calls. This way, the server is the only one responsible for the Auth cookie, and can verify the authentication with Request.IsValidated.
Related
I'm using a server that authenticates through keycloak. In keycloak I have created a realm and am able to get an Access token as a response. This access token gets fed now into my Ktor application.
However, I'm not quite sure how to protect routes in an easy manner. I want to have some protected routes that have a authenticate("keycloakOAuth"){} scope around it which handles validating the access token and refreshing using the refresh token if the access token is expired.
Currently I have keycloak inside Ktor configured as this:
authenticate("keycloakOAuth") {
get("login") {}
route("/callback") {
// This handler will be executed after making a request to a provider's token URL.
handle {
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
if (principal != null) {
val response = principal as OAuthAccessTokenResponse.OAuth2
call.respondText { "Access token: ${response.accessToken}" }
} else {
call.respondText { "NO principal" }
}
}
}
}
This works fine because when I go to login I'm getting sent to the Keycloak login page and I can login. When I logged in the callback executes and I get my Access Token back.
When I'm trying to protect routes however, some odd stuff happens. I know that I need to validate the incoming JWT token. But I have no clue how to given the Ktor capabilities. The examples are also of little help, since they are quite vague.
Currently I have something like this:
authenticate("keycloakOAuth") {
get("/testAuth") {
val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
if(principal != null) {
call.respondText("Authenticated!")
} else {
call.respondText("Unauthenticated...")
}
}
}
But my application will always send me to the login page and then callback page, even though I am sending the Bearer token when I'm testing this call.
My question is:
How do I protect routes in a manner that they need a valid token, with the same syntax that Ktor uses (like authenticate(){}). Do I need to configure JWT for this?
When you request one of the routes under authenticate, the full cycle of OAuth authentication is triggered. This is because the Authentication plugin is designed so a client sends credentials and gets authenticated for each request. For some reason, OAuth integration was implemented on top of the Authentication plugin hence such unexpected behavior.
To solve your problem you can have only /login and /callback routes restricted. In the callback's handler save user ID and tokens in a session or in any other storage for future use. For other routes, you can check manually the fact that a user is authenticated and then use tokens from storage to acquire protected data from the resource server. For convenience, you can create some extension functions to minimize the amount of boilerplate code. Unfortunately, there is no built-in functionality to make it work out of the box.
You don't need to configure JWT for this.
I am trying out JWT in ASP.NET Core but I am stumped on how to use it in page navigation. I have several controllers marked with Authorize attribute and I need to include the JWT in the header of the request to be able to access them.
I already am generating the token, I just don't know how I would include the token in the request header when navigating to a page marked with Authorize.
My questions are:
How do I add the token to the request so I can access the pages with Authorize upon navigation?
Are JWT tokens typically used in this fashion (page navigation), or mainly for API calls? Is it wrong to use it like this?
I need to be able to store the JWT so I can add them in the request header and I am thinking to store in cookie. Doesn't this give it the problem that cookie-based authentication have regarding CSRF?
JWT Tokes are for webservice/Rest API calls.
For MVC pages you use Cookies, typically done via ASP.NET Core Identity.
You can also use JWT from Controllers, i.e. if you call a remote api. In this case you build your request via HttpClient and SetBearerToken, like this
public async Task<IActionResult> RemoteCall()
{
string accessToken = ...;
using (var client = new HttpClient())
{
client.SetBearerToken(accessToken);
string content = await client.GetStringAsync("https://example.com/api/resource");
return Content(content);
}
}
Scenario:
Both Web application and Web API need to be authenticated and protected from the server side.
Requirement:
Web application is serving the contents for the browser and browser should be calling Web API directly (i.e. Browser to API).
Question:
Is it possible to authenticate both Web APP and the API using tokens?
Any sample code or clear direction would be highly appreciated.
Normally web applications are authenticated using cookies and APIs are authenticated using tokens.There are some sample projects available here but they are either browser to API (SPA token based) or Server side Web App calling API from server to server.
UPDATE 1
App is saving the TokenValidationParameters and used bootstrapContext.Token within the app controller to grab for server to server communication.
As per #dstrockis, I'm trying to grab the id_token from the Web App soon after the end of validation (not within the app contrller).
I'm using SecurityTokenValidated invoker in OpenIdConnectAuthenticationOptions.Notifications within the Startup class. SecurityTokenValidated receives a parameter of type SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> but I'm not sure where to find the id_token within it. Method is below.
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
//NEW METHOD INVOKE ************************************
//******************************************************
SecurityTokenValidated = OnSecurityTokenValidated
//******************************************************
},
Scope = "openid",
ResponseType = "id_token",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
SaveSigninToken = true
},
};
}
//NEW METHOD ************************************
private Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> arg)
{
//QUESTION ********************************************************
//How to find the just saved id_token using incoming parameter, arg
//*****************************************************************
return Task.FromResult(0);
}
UPDATE 2
Instead of SecurityTokenValidated, I tried AuthorizationCodeReceived and it's not getting called at all. As discussed here, my redirect url does have an ending slash as well.
Any Ideas?
Our ASP.NET OpenID Connect middleware which supports AAD B2C is built to rely on cookie authentication from a browser. It doesn't accept tokens in a header or anything like that for securing web pages. So I'd say if you want to serve HTML from your web app in the classic way, you need to use cookies to authenticate requests to the web app.
You can definitely get & store tokens within the browser and use those to access your web API, even if you use cookies to authenticate to the web app. There's two patterns I'd recommend:
Perform the initial login using the OpenID Connect Middleware, initiating the flow from the server side as described in the samples. Once the flow completes, the middleware will validate the resulting id_token and drop cookies in the browser for future requests. You can instruct the middleware to save the id_token for later use by using the line of code written here. You can then somehow pass that id_token down to your browser, cache it, and use it to make requests to the API.
The other pattern is the inverse. Start by initiating the login from javascript, using the single page app pattern from the B2C documentation. Cache the resulting id_tokens in the browser, and use them to make API calls. But when the login completes, you can send a request to your web app with the id_token in the body, triggering the OpenID Connect middleware to process the request and issue a session cookie. If you want to know the format of that request, I'd recommend inspecting a regular server side OpenID Connect flow.
Found the answer to my own question and adding here for the future reference.
After a successful validation, id_token can be accessed by invoking the SecurityTokenValidated notification. Code sample is below.
private Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> arg)
{
//Id Token can be retrieved as below.
//**************************************
var token = arg.ProtocolMessage.IdToken;
return Task.FromResult(0);
}
However, saving this directly into a browser cookie may not be secure.
I've got a WebAPI instance running in Azure that is secured with Azure AD. A mobile app connects to this API using bearer tokens, works great. When I try calling the API from the browser, though, it returns a 401 because I'm not logged in. That's true because I'm not presented with a login screen.
My API doesn't have any UI so what I'd want it to do is to forward the user to Azure AD login and return to the API endpoint they were calling after authentication.
If I go to the Azure portal, there's a setting that says "Action to take when the request is not authorized". If I set that one to "Log in with Azure Active Directory", it behaves the way I want it to. But... I have some endpoints which need to be accessed anonymously, and this setting catches all requests, not caring about any [AllowAnonymous] attributes.
So any request to an endpoint labeled Authorize that is not authorized yet should be forwarded to Azure AD login, all others should be allowed.
Add a DelegatingHandler to your web api project and register it in WebApiConfig.cs:
config.MessageHandlers.Add(new UnAuthorizedDelegatehandler());
public class UnAuthorizedDelegatehandler: DelegatingHandler
There you can check for 401 status codes and do the redirect to whatever and also apply a redirect url as querystring parameter.
HttpResponseMessage rm = await base.SendAsync(request, cancellationToken);
if (rm.StatusCode == HttpStatusCode.Unauthorized)
{
// configure the redirect here
return rm;
}
I'll try to be explicit.
I have a Front End app (MVC), it can communicate with a Facade Web Api (Resource) and get token from an authentication server.
My problem is I can't create an identity in MVC app. This is mu Startup class.
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
When I try to go to a method controller decorated with [Authorize], I get 401 error instead to be redirected to Login page.
Please, I would appreciate any help, advice or example.
Regards,
Typically, unless your app is doing postback's you do not need to enable the cookie authentication with login path. That is for the oauth password login flow (grant_type) where you are internally authorizing your users against your identity database. If you're redirecting to an external authorization api (like facebook) then you don't need to set a login path in your application since the first authorization endpoint that gets hit is your external callback (after you send them to facebook, they will send them back to your external endpoint). The redirect you are getting is because cookie authentication is registered as active authentication mode so it redirects 401's to the login path you set (overriding other OWIN middleware).
If you want to house your authorization server in house, have a look at the link below, it will at the least get you setup with JWT support -
http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/