ASP.NET Core 1.0 OAuth Server using Openiddict
Asked Answered
A

2

7

I would like to use Openiddict OAuth to protect api endpoints in my ASP.NET Core 1.0 Web Application. The api endpoints will be called by a phone app and users must login with username and password.

The flow goes like this:

  • User can register and login via web application: https://www.domain.com
  • User install phone app, and they can login and register using the phone app. Login, registration and data access is done via api endpoints: Example: https://www.domain.com/api/service/getsomedata

How can I configure Openiddict OAuth so I can protect the API endpoints using OAuth?

Ambages answered 18/7, 2016 at 7:29 Comment(0)
A
5

How can I configure Openiddict OAuth so I can protect the API endpoints using OAuth?

Your scenario sounds like a good candidate for the simple "resource owner password credentials" grant, which is basically the OAuth2 equivalent of basic or forms authentication.

Here's what I'd recommend:

Create a new AccountController/RegistrationController API controller responsible of creating new accounts:

Since the user account doesn't exist at this stage, you can't use token authentication here (just like the default AccountController.Register template cannot require cookies authentication before the user is registered).

Configure OpenIddict to enable the token endpoint and allow the resource owner password credentials grant:

services.AddOpenIddict<ApplicationDbContext>()
    // Disable the HTTPS requirement during development.
    .DisableHttpsRequirement()

    // Enable the token endpoint, required to use
    // the resource owner password credentials grant.
    .EnableTokenEndpoint("/connect/token")

    // Enable the password and the refresh token flows.
    .AllowPasswordFlow()
    .AllowRefreshTokenFlow();

Use the OAuth2 validation middleware to protect your APIs:

To enable token authentication, reference AspNet.Security.OAuth.Validation 1.0.0-alpha2-final package and add app.UseOAuthValidation() before app.UseMvc(). To make authentication mandatory, simply use the [Authorize] attribute like you'd do with cookies authentication.

Don't hesitate to play with this sample. It doesn't use a mobile app for the client-side part, but you should easily understand how it works.

For more information, you can also read this blog post, written by Mike Rousos for the Microsoft .NET Web Development and Tools blog: Bearer Token Authentication in ASP.NET Core

Ancestral answered 19/7, 2016 at 18:3 Comment(0)
A
1

Ok, Thanks @Pinpoint for pointing me to the right direction.

However here is my Startup.cs configuration:

   public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

        if (env.IsDevelopment())
        {
            // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
            builder.AddUserSecrets();
        }

        builder.AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, ApplicationRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.AddOpenIddict<ApplicationUser, ApplicationRole, ApplicationDbContext>()
          .DisableHttpsRequirement()
          .EnableTokenEndpoint("/connect/token")
          .AllowPasswordFlow()
          .AllowRefreshTokenFlow()
          .UseJsonWebTokens();

        services.AddMvc();

        // Add application services.
        services.AddTransient<IEmailSender, AuthMessageSender>();
        services.AddTransient<ISmsSender, AuthMessageSender>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {


        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
            app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();      

        app.UseIdentity();

        app.UseOpenIddict();

        app.UseJwtBearerAuthentication(new JwtBearerOptions
        {
            AutomaticAuthenticate = true,
            AutomaticChallenge = true,
            RequireHttpsMetadata = false,
            Audience = "http://localhost:24624/",
            Authority = "http://localhost:24624/"
        });


        // Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

ApplicationDbContext.cs:

  public class ApplicationDbContext : OpenIddictDbContext<ApplicationUser, ApplicationRole>
{
    public ApplicationDbContext(DbContextOptions options)
        : base(options)
    {
        Database.EnsureCreated();
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}

ApplicationRole.cs:

public class ApplicationRole : IdentityRole
{
}

ApplicationUser.cs:

  public class ApplicationUser : OpenIddictUser
{
}

ServiceController.cs:

 [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]
[Route("api/service")]
public class ServiceController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;

    public ServiceController(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    [HttpGet]
    [Route("getdata")] 
    public async Task<IActionResult> GetData()
    {
        var user = await _userManager.GetUserAsync(User);
        if (user == null) return Ok("No user / not logged in");// if Authorize is not applied
        return Ok(user);
    }
}

The key in here is the ServiceController.cs: [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]

@Pinpoint: I didn't use app.UseOAuthValidation() because it was returning 302 and redirect to Account/Login.

So now it works like this:

  • accessing the http://domain.com, user can register, login, see data, etc.
  • user can download mobile app, register, login and get data

Implementing the user registration login in the api side is preatty easy and straight forward.

The problem was that using fiddler and issuing a GET to http://domain.com/api/service/getdata was returning a 302 and redirect to Account/Login. If I remove app.UseIdentity(), then if will have returned 401 Unauthorized but user would have not been able to login anymore using the UI http://domain.com. Adding this [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)] to my ServiceController solved the problem.

@Pinpoint what was the benefit of app.UseOAuthValidation() ?

Ambages answered 19/7, 2016 at 21:12 Comment(2)
Check out this answer #41551930, using app.MapWhen is another way so that you do not have to [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]Wheeling
I'm wondering if it is possible to make "ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme" as default, so that I do not hat to set it on every Controller.Influx

© 2022 - 2024 — McMap. All rights reserved.