.NET Core 2.0 dual authentication (Mixing MVC and API) - asp.net-mvc

I am a back-end developer who typically develops APIs and and administration panels all under the same project. I primary use Laravel/PHP, however I recently started delving into .NET Core.
In PHP (Laravel), I could tie my API endpoints and webpages to the same controller actions. For example, An API consumer should be able to create a blog post using the API endpoint. Also, the administrator should be able to create a blog post using the web UI, which should follow the same validation logic, etc.
Here is some pseudocode for the create action.
class BlogPostController
{
//Create a new blog post
function create(request)
{
authorizeAction(); //Make sure the current user is authorized to create a blog post
validateFields(request); //Make sure the posted data is valid for a blog post
//Create the blog post
blogPost = new BlogPost(request);
if(request.isAPI)
return json(blogPost); //Return the new blog post as a json string
return view(blogPost); //Return an HTML representation of the blog post (in administration panel)
}
}
In Laravel, since all routes can have different middleware, I could define two routes to the above action eg POST /api/blogpost and POST /blogpost, each with different authentication schemes. (api would authorize via a JWT token, and the web url would auth via a cookie).
I'm having trouble doing something similar in .NET Core since different authentication schemes cannot be applied via separate middleware as of Version 2 (as far as I know).
My solution to this problem would be adding two authentication schemes like so:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(cfg=>cfg.SlidingExpiration = true)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
ClockSkew = TimeSpan.Zero
};
});
And then on my controller use:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme + "," + CookieAuthenticationDefaults.AuthenticationScheme)]
This would effectively challenge a JWT token and a cookie and use whichever one it found. This seems a bit messy to me (and a Microsoft employee would agree)
Am I approaching this whole problem wrong? To me it makes sense to keep all of the logic in one controller and just return json or HTML depending on the type of request. I could have a separate controller for the API endpoints and the webpages, but it seems like a lot of duplication of code. The other option would be to ditch the idea completely and just build an API/Single Page Application that consumes the API.
Is this simply bad design, or is there any elegant way of using different authentication schemes based on the route?

Related

Function to request user claims and token from Identity Server?

What is the best way to retrieve user claims and tokens after logging into identity server?
I have a .Net Core MVC web app. I redirect to an identity server, log in, then redirect back to my web app.
From my app I would then like to request user claims and token from identity server.
Is there a method call to do this?
I have gone through the tutorials and searched around and found libraries (oidc-client-js, oidc.usermanager for javascript that handles this. OidcClient for native c# apps) that handle this behavior, but not for c# web app - so I realize maybe what I'm asking for is not the convention but I'd still appreciate any insight on this.
Edit:
To elaborate, in the c# web app tutorials, specifically tutorial #3, I fail to see how claims and token information become retrievable from from figure 1 to figure 2.
Furthermore, I'm trying to call and handle these objects in c#, not html.
Figure 1
Figure 2
Thank you
In controller , you can retire claims like
var claims = User.Claims;
Get the access token/id token/refresh token :
var accessToken = HttpContext.GetTokenAsync("access_token").Result;
var idToken = HttpContext.GetTokenAsync("id_token").Result;
var refreshToken = HttpContext.GetTokenAsync("refresh_token").Result;
To achieve that , you need register your OIDC middleware inside ConfigureServices as follows (making sure to set SaveTokens to true) :
.AddOpenIdConnect(options => {
// Set all your OIDC options...
// and then set SaveTokens to save tokens to the AuthenticationProperties
options.SaveTokens = true;
});
In mvc razor page , you can get the information :
#using Microsoft.AspNetCore.Authentication
#{
ViewData["Title"] = "Home Page";
}
<dl>
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
</dl>
<dt>access token</dt>
<dd>#await ViewContext.HttpContext.GetTokenAsync("access_token")</dd>
<dt>ID token</dt>
<dd>#await ViewContext.HttpContext.GetTokenAsync("id_token")</dd>
<dt>refresh token</dt>
<dd>#await ViewContext.HttpContext.GetTokenAsync("refresh_token")</dd>

A simple ASP .NET MVC API controller using roles

I wrote a web application using ASP .NET MVC and authorization system by default. I configured IdentityRole and input through external providers. Using the current database I have created my data context. Now I want to write a Xamarin.Android app and connect to my database, I want a simple API. But the feature that you want to access this API was only available to user with a certain role. The API is really very simple and therefore do not want to add to the draft WCF or WebAPI project. How to do it best?
First, you don't need a separate project to use Web Api; you can use both MVC and Web Api in the same project. For one off endpoints for things like in-site AJAX requests, just creating MVC actions that return JSON or XML would be fine, but if you're talking about a true API, even if it's fairly simplistic, I'd say go Web Api.
You'd protect your Web Api actions much the same as you would your MVC actions, using the [Authorize] attribute. If you need to restrict by role, you just pass a role(s) to that. However, the big difference here, especially if you're serving a mobile app, is that you'll need pass the authorization along with the request. That's generally accomplished using the Authorization header along with a bearer token. Basically, you would need to set up an endpoint that signs a user in and returns a token. Then, each subsequent request that needs authorization includes that token in the header.
I want to finish and to fully answer this question and close this topic. I've been searching for how to add the ability for a mobile client to connect to an existing site on ASP.NET MVC. In my search, I came across a great article Justin Hyland on March 2, 2014
In principle, everything in this article is well and clearly written, but I want to make a tiny contribution for clarity.
Under Setup WebAPIConfig stated that the need
added in the following code to the WebApiConfig Register method
But if we consider the case ASP.NET MVC we don't have such file. It's all very simple, you just need such a file to create the folder App_Start. The contents of the file can be left exactly as it is in the article.
To get rid of the bugs which will inevitably appear we need to install two nuget package: Microsoft.AspNet.WebApi and Microsoft.AspNet.WebApi.Owin.
Excellent! Now we can turn to the method to obtain the token and then adding the token to the query we can get the needed data closed by the attribute [Authorize].
A small remark. If You need to access a method which is closed for a specific role that to the Authenticate method from the article should add a few lines of code. Immediately after the line:
identity.AddClaim(new Claim(ClaimTypes.Name, user));
add the line:
identity.AddClaim(new Claim(ClaimTypes.Role, role));
where role you can get the following, for example:
var userIdentity = UserManager.FindAsync(user, password).Result;
var role = RoleManager.FindById(userIdentity.Roles.First().RoleId).Name;
User and password you have to send a request.
I also want to give an example of code which will send request and receive response. To not have to look for and immediately start coding.
async Task<string> GetToken(string userName, string password)
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>( "user", userName ),
new KeyValuePair<string, string> ( "password", password )
}
);
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.PostAsync(APP_PATH + "/Authenticate", content);
var result = await response.Content.ReadAsStringAsync();
return result;
}
}
async Task<string> GetUserInfo(string token)
{
using (var client = CreateClient(token))
{
var response = await client.GetAsync(APP_PATH + "/ValidateToken");
return await response.Content.ReadAsStringAsync();
}
}
HttpClient CreateClient(string accessToken = "")
{
var client = new HttpClient();
if (!string.IsNullOrWhiteSpace(accessToken))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
return client;
}
All have only to call the appropriate methods in the correct order. I hope that is useful to someone.
P.S.
If You create a new project in Visual Studio to get this functionality you just need to tick:

Combine the use of authentication both for MVC pages and for Web API pages?

I have an MVC 5 web application and can login with a Login.cshtml page and get a cookie and the login works fine. But, I would like to do a login with the Web API and then (maybe) set a cookie so that I am logged in for my MVC pages... (or login with the MVC login and then access the Web API) however the web api returns a bearer token and not a cookie token... so this doesn't work. Is there a way to combine the use of authentication both for my MVC pages and for my Web API pages?
UPDATE:
This isn't really a code issue, more of a conceptual issue.
Normal MVC web pages examine a cookie named, by default, ".AspNet.ApplicationCookie" to determine the requesters identity. This cookie is generated by calling ApplicationSignInManager.PasswordSignInAsync.
WebAPI calls, on the other hand, examine the requests headers for an item named Authorization... and uses that value to determine the requesters identity. This is returned from a WebAPI call to "/Token".
These are very different values. My website needs to use both MVC pages and WebAPI calls (to dynamically update those pages)... and both need to be authenticated to perform their tasks.
The only method I can think of is to actually authenticate twice... once with a WebAPI call and again with the Login post. (see my Answer below).
This seems very hacky... but I don't understand the authorization code enough to know if there is a more proper way of accomplishing this.
The best way to achieve this, is to have an authorization server (a Web API generating a token) and token consumption middle-ware in your MVC project. IdentityServer should help. However I have done it like this:
I built an authorization server using JWT with Web API and ASP.Net Identity as explained here.
Once you do that, your Web APIs startup.cs will look like this:
// Configures cookie auth for web apps and JWT for SPA,Mobile apps
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
// Cookie for old school MVC application
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true, // JavaScript should use the Bearer
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/api/Account/Login"),
CookieName = "AuthCookie"
};
// Plugin the OAuth bearer JSON Web Token tokens generation and Consumption will be here
app.UseCookieAuthentication(cookieOptions);
OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["JWTPath"])
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
You can find the CustomOAuthProvider and CustomJwtFormat classes here.
I wrote a consumption logic (i.e. middleware) in all my other APIs (Resource servers) that I wanted to secure using the same token. Since you want to consume the token generated by the Web API in your MVC project, after implementing the authorization server, you need to the following:
In your MVC app, add this in startup.cs:
public void Configuration(IAppBuilder app)
{
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["AuthIssuer"];
string audienceid = ConfigurationManager.AppSettings["AudienceId"];
byte[] audiencesecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);
app.UseCookieAuthentication(new CookieAuthenticationOptions { CookieName = "AuthCookie" , AuthenticationType=DefaultAuthenticationTypes.ApplicationCookie });
//// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = "JWT",
AllowedAudiences = new[] { audienceid },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audiencesecret)
}
});
}
In your MVC controller, when you receive the token, de-serialize it and generate a cookie from the access token:
AccessClaims claimsToken = new AccessClaims();
claimsToken = JsonConvert.DeserializeObject<AccessClaims>(response.Content);
claimsToken.Cookie = response.Cookies[0].Value;
Request.Headers.Add("Authorization", "bearer " + claimsToken.access_token);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync("JWT");
ctx.Authentication.SignOut("JWT");
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);
Generate a machine key and add it in web.config of your Web API and ASP.Net MVC site.
With this, a cookie will be created and the [Authorize] attribute in the MVC site and the Web API will honor this cookie.
P.S. I have done this with a Web API issuing JWT (Authorization server or Auth & resource server) and was able to consume it in an ASP.Net MVC website, SPA Site built in Angular, secure APIs built in python (resource server), spring (resource server) and an Android App.
Ugg... what I had to do was use the Login.cshtml form and override the submit... make an Ajax call to get the WebApi bearer token... and then do the form submit to get the actual MVC cookie. So, I'm actually making two login requests... one for the WebApi token and the other for the MVC cookie.
Seem pretty hacky to me... it would be nice if there was some way to sign in to MVC using the bearer token... or a call to the WebApi that would return me a cookie that I can use for normal MVC page requests.
If anyone has a better way I would love to hear it.
This is script code that I added to Login.cshtml:
$(document).ready(function () {
$('form:first').submit(function (e) {
e.preventDefault();
var $form = $(this);
var formData = $form.serializeObject(); // https://github.com/macek/jquery-serialize-object
formData.grant_type = "password";
$.ajax({
type: "POST",
url: '#Url.Content("~/Token")',
dataType: "json",
data: formData, // seems like the data must be in json format
success: function (data) {
sessionStorage.setItem('token', data.access_token);
$form.get(0).submit(); // do the actual page post now
},
error: function (textStatus, errorThrown) {
}
});
});
});
I assume what you're trying to do is have pages served by MVC have javascript that makes calls to Web API methods. If you're using ASP.NET Identity to handle authentication (which it looks like you're doing), then MVC should be using OAuth tokens that can be passed to Web API for authentication.
Here's a snippet from some javascript code that works for me in a similar situation:
var token = sessionStorage.getItem('access_token');
var headers = {};
if (token) {
headers.Authorization = 'Bearer ' + token;
}
$.ajax({
type: <GET/POSt/...>,
url: <your api>,
headers: headers
}).done(function (result, textStatus) {
I have similar case with you, but i use a different way to authenticate.
I have a web and an api, which all for intranet users. I do not use user's identity to pass web and api. Instead, i created a individual web account, and every time web will use this special account to connect to api.
Because, we also need to make sure that users should not connect to api directly. They should only connect to web ui.
Hope this help you.
From your comments above, from what I understand, you have a scenario wherein you perform login through browser but also have to invoke web-api methods using ajax calls.
The browser calls are session-cookie based. While the ajax calls from the browser would have the session cookie in the header, what is required is the authentication header to be present for the web-api to perform validation.
So on a successful login you'd also have to generate a web-api based token, set it as a cookie (that is accessible by javascript) and then while making ajax calls, pick it up from the cookie and include it as header in your 'Authorization' header.

ASP .Net MVC and WCF Identity (Claims) Integration

We're building a platform where the client is an ASP .Net MVC one, using ASP Net Identity 2.0 for authentication and authorization (using Claims), which works great on the web side.
We also have a WCF service which allows CRUD operations on the database (for multiple client applications), which gets requests from this ASP .Net MVC client.
As we want to validate (authenticate & authorize) the user before making specific CRUD actions in the WCF side, we need to get the claims of the user from the client, and perform the validations (preferably in a very clean manner using headers or any binding that WCF will be able to support for this matter).
I've been searching the different forums but with no simple answer\tutorial to this specific scenario. Can anyone assist on this matter?
Thanks,
Nir.
I love this:
in your IEndpointBehavior implementation do this on the client end:
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Add(MessageHeader.CreateHeader("token", "http://myurl.com/service/token", _theToken));
return null;
}
then on the service end add this to your ServiceAuthenticationManager
public override ReadOnlyCollection<IAuthorizationPolicy> Authenticate(
ReadOnlyCollection<IAuthorizationPolicy> authPolicy, Uri listenUri, ref Message message)
{
IPrincipal user = new MyUserPrincipal(null);
if(_currentServiceContractType.GetInterfaces()
.Any(x => x == typeof(IMySecuredService)))
{
var tokenPosition = message.Headers.FindHeader("token", "http://myurl.com/service/token");
if (tokenPosition >= 0 && tokenPosition <= 5)
{
var encryptedToken = message.Headers.GetHeader<string>(tokenPosition);
if (!string.IsNullOrWhiteSpace(encryptedToken))
{
var serializedToken = new MyEncryptionUtility().Decrypt(encryptedToken);
var token = MyTokenSerializer.Deserialize(serializedToken);
var expire = new DateTime(token.ValidToTicks);
if (expire > DateTime.Now)
{
user = new MyUserPrincipal(token);
}
}
}
}
message.Properties["Principal"] = user;
Thread.CurrentPrincipal = user;
return authPolicy;
}
This gives you then the ability to use the built in claims or WIF claims authentication. Eitherway, this is very simple. The token is created by the service and sent to the client (web) and stored in the cookie. then when there are any requests, the token is grabbed from a cookie and then sent along to the service, where, inevitably you can start adding permissions service side, versus doing them on the web/mvc side, making a much cleaner code base using everyone's favorite friend, SOA >= :)

Registering and Storing External Claims in ASP.NET Identity

I'm using Facebook auth and trying to register the user token as a claim but can't seem to get it to work right. I was able to get it working by manually adding it in the LinkLoginCallback method of the Manage controller provided by the default MVC template, but that approach seems cumbersome and incorrect, especially in removing and updating the token.
I've read everything I can find but the documentation on this is really lacking and I think I have the code written as it's supposed to be but it never actually registers and stores the claim in the DB. The solution I'm working with at the moment is as follows in my Startup.Auth:
var fbOptions = new FacebookAuthenticationOptions();
fbOptions.Scope.Add("email");
fbOptions.Scope.Add("user_photos");
fbOptions.AppId = "[myappid]";
fbOptions.AppSecret = "[myappsecret]";
fbOptions.Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = async context =>
{
context.Identity.AddClaim(
new System.Security.Claims.Claim("FacebookAccessToken",
context.AccessToken));
}
};
fbOptions.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseFacebookAuthentication(fbOptions);

Resources