9

I have an MVC web app, and a WPF client and Web API server. I would very much like to pull the API controllers into the MVC app, and have only one app/api. My main reason for this is to have one sign-in procedure for application users and API clients alike.

What I would have liked to do is inject a SignInManager<ApplicationUser into the Token controller and use

var result = await _signInManager.PasswordSignInAsync(login.Username, login.Password, false, false);

to authenticate the username and password using the same password hasher as build into the MVC project. I don't want to duplicate any of that type of functionality. I also hope, with this, to achieve a sign-in that gives a JWT token, for the WPF app to access the API side of things, and a standard signed-in cookie, for normal MVC app requests to be authorized.

How should I go about this with minimum code duplication?

IDEA: Use one MVC application as a REST server as well. If my Login action that gets the JWT also signs me in, e.g. with _signInManager.PasswordSignInAsync, I should also get the cookie that will allow me to access protected actions internally, and use them externally with the authorization header containing the JWT.

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
ProfK
  • 49,207
  • 121
  • 399
  • 775
  • 3
    Basically, you should factor out all common code into a class library that both projects can utilize. That will likely mean moving all your entities, context, Identity setup etc. You do not need separate *accounts*; a single user can be authenticated/authorized in multiple ways in multiple different scenarios. That part is separate from the actual account persistence for a reason. However, if you want to use things like JWTs, you might have some manual work to do. – Chris Pratt Mar 13 '18 at 15:26
  • 3
    It might be better to use something like IdentityServer, which decentralizes your authentication and authorization and handles multiple different auth workflows out of the box. – Chris Pratt Mar 13 '18 at 15:26
  • The above comments were made before I completely revised the question, in case anyone questions relevance. – ProfK Mar 15 '18 at 08:40
  • 1
    @ProfK It is definitely doable. Just a lot to do. A monolith web application that shares MVC and Web API both using identity is possible. I say so because I've done it before. Which is why I say it is a lot to do. Way too broad to cover here. – Nkosi Mar 15 '18 at 12:53
  • Are both MVC app and API are running ASP.NET Core? – Alexey Andrushkevich Mar 15 '18 at 18:30
  • @AlexeyAndrushkevich Yes, both are running Core 2. – ProfK Mar 16 '18 at 02:36

1 Answers1

12

You can consider the following options

Option #1: Move the logic which is used in MVC app to new class library which then can be referenced by API project. This will allow you to follow SRP and use the same logic in both MVC and API apps. If you want to make ASP.NET Identity related functionality to be common for MVC and API apps take a look at Moving ASP.NET Identity Model to Class Library and follow provided steps.

Option #2: You can move all controllers and models from API to MVC app. You can configure then API controllers to use JWT authentication scheme and continue using cookie authentication scheme fr MVC apps' controllers. If you want to follow this approach you will need to configure your Authentication middleware. For example your Startup class would look something like this:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddMvc();

    services.AddAuthentication(o =>
    {
        o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    }).AddCookie(options =>
    {
        options.AccessDeniedPath = new PathString("/Account/Login/");
        options.LoginPath = new PathString("/Account/Login/");
    }).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => {
        options.TokenValidationParameters = new TokenValidationParameters 
        {       
            ValidateAudience = false,
            ValidateIssuer = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("<put_your_secret_here>")),
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromMinutes(5)            
        };
    });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    app.UseAuthentication();
    app.UseMvcWithDefaultRoute();
    ...
}    

Then you can define BaseApiController which can be used to setup a route to API controllers and authorize them using e.g.

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
public abstract class BaseApiController {}

and make all API controllers inherit this base controller. You can also use policy based authorization which is more flexible. After you accomplish you will be able to authenticate with cookie in MVC app controllers and with JWT token in API controllers.

Option #3: You can leave API and MVC app running separately and use IdentityServer to implement OAuth protocol. Add it to MVC app since it integrates well with ASP.NET Identity and configure it so that to continue using the cookie authentication scheme for MVC app. Then configure one of the OAuth flow to setup the authorization between WPF app and API. IdentityServer is well documented and provides samples for different flows in their GitHub. The advantage of this approach is that you will never have such problem in future even if you consider adding more APIs, SPA or MVC apps which will have to use the same user base. Also it allows to implement Single Sign On(SSO) so if you ever need another MVC app it will be easy integrate it into existing infrastructure.

Alexey Andrushkevich
  • 5,992
  • 2
  • 34
  • 49
  • Thanks for a comprehensive answer, however, one part of option #2, my preferred option, has the code `new TokenValidationParameters {...}`. Just what is the `...`? – ProfK Mar 24 '18 at 07:18
  • I have updated my answer with example of how `TokenValidationParameters` can be set. – Alexey Andrushkevich Mar 24 '18 at 11:28