The State of Security in ASP.NET 5 and MVC 6: OAuth 2.0, OpenID Connect and IdentityServer

ASP.NET 5 contains a middleware for consuming tokens – but not anymore for producing them. I personally have never been a big fan of the Katana authorization server middleware (see my thoughts here) – and according to this, it seems that the ASP.NET teams sees IdentityServer as the replacement for it going forward.

So let’s have a look at the bits & pieces and how IdentityServer can help in implementing authentication for MVC web apps and APIs.

IdentityServer
IdentityServer is an extensible OAuth 2.0 authorization server and OpenID Connect provider framework. It is different from the old authorization server middleware as it operates on a higher level of abstraction. IdentityServer takes care of all the protocol and data management details while you only need to model your application architecture using clients, users and resources (see the big picture and terminology pages).

IdentityServer is currently available as OWIN middleware only (as opposed to native ASP.NET 5 middleware) – that means it can be used in Katana-based hosts and ASP.NET 5 hosts targeting the “full .NET framework” aka DNX451. It does currently not run on the Core CLR – but we are working on it.

There is an integration package for bridging OWIN into ASP.NET 5 which allows wiring up IdentityServer using the typical application builder extension method pattern:

var certFile = env.ApplicationBasePath + "\\idsrv3test.pfx";
 
var idsrvOptions = new IdentityServerOptions
{
    Factory = new IdentityServerServiceFactory()
                    .UseInMemoryUsers(Users.Get())
                    .UseInMemoryClients(Clients.Get())
                    .UseInMemoryScopes(Scopes.Get()),
 
    SigningCertificate = new X509Certificate2(certFile, "idsrv3test"),
};
 
app.UseIdentityServer(idsrvOptions);

For this sample we use in-memory data sources, but you can connect to any data source you like.

The Users class simply defines two test users (Alice and Bob of course) – I won’t bore you with the details. Scopes on the other hand define the resources in your application, e.g. identity resources like email addresses or roles, or resource scopes that represent your APIs:

public static IEnumerable<Scope> Get()
{
    return new[]
    {
        // standard OpenID Connect scopes
        StandardScopes.OpenId,
        StandardScopes.ProfileAlwaysInclude,
        StandardScopes.EmailAlwaysInclude,
 
        // API - access token will 
        // contain roles of user
        new Scope
        {
            Name = "api1",
            DisplayName = "API 1",
            Type = ScopeType.Resource,
 
            Claims = new List<ScopeClaim>
            {
                new ScopeClaim("role")
            }
        }
    };
}

Clients on the other hand represent the applications that will request authentication tokens (also called identity tokens) and access tokens.

public static List<Client> Get()
{
    return new List<Client>
    {
        new Client
        {
            ClientName = "Test Client",
            ClientId = "test",
            ClientSecrets = new List<Secret>
            {
                new Secret("secret".Sha256())
            },
 
            // server to server communication
            Flow = Flows.ClientCredentials,
 
            // only allowed to access api1
            AllowedScopes = new List<string>
            {
                "api1"
            }
        },
 
        new Client
        {
            ClientName = "MVC6 Demo Client",
            ClientId = "mvc6",
 
            // human involved
            Flow = Flows.Implicit,
 
            RedirectUris = new List<string>
            {
                "http://localhost:19276/signin-oidc",
            },
            PostLogoutRedirectUris = new List<string>
            {
                "http://localhost:19276/",
            },
 
            // access to identity data and api1
            AllowedScopes = new List<string>
            {
                "openid",
                "email",
                "profile",
                "api1"
            }
        }
    };
}

With these definitions in place we can now add an MVC application that uses IdentityServer for authentication, as well as an API that supports token-based access control.

MVC Web Application
ASP.NET 5 includes middleware for OpenID Connect authentication. With this middleware you can use any OpenID Connect compliant provider (see here) to outsource the authentication logic.

Simply add the cookie middleware (for local signin) and the OpenID Connect middleware pointing to our IdentityServer to the pipeline. You need to supply the base URL to IdentityServer (so the middleware can fetch the discovery document), the client ID, scopes, and redirect URI (compare to our client definition we did earlier). The following snippet requests the user’s ID, email and profile claims in addition to an access token that can be used for the “api1” resource.

app.UseCookieAuthentication(options =>
{
    options.AuthenticationScheme = "Cookies";
    options.AutomaticAuthenticate = true;
});
 
app.UseOpenIdConnectAuthentication(options =>
{
    options.AutomaticChallenge = true;
    options.AuthenticationScheme = "Oidc";
    options.SignInScheme = "Cookies";
 
    options.Authority = "http://localhost:5000";
    options.RequireHttpsMetadata = false;
 
    options.ClientId = "mvc6";
    options.ResponseType = "id_token token";
 
    options.Scope.Add("openid");
    options.Scope.Add("email");
    options.Scope.Add("api1");
});

MVC Web API
Building web APIs with MVC that are secured by IdentityServer is equally simple – the ASP.NET 5 OAuth 2.0 middleware supports JWTs out of the box and even understands discovery documents now.

app.UseJwtBearerAuthentication(options =>
{
    options.Authority = "http://localhost:5000";
    options.RequireHttpsMetadata = false;
 
    options.Audience = "http://localhost:5000/resources";
    options.AutomaticAuthenticate = true;
});
 
app.UseMiddleware<RequiredScopesMiddleware>(new List<string> { "api1" });

Again Authority points to the base-address of IdentityServer so the middleware can download the metadata and learn about the signing keys.

Since the built-in middleware does not understand the concept of scopes, I had to fix that by adding the extra scope middleware.

Expect this be improved in future versions.

API Client
Tokens can be also requested programmatically – e.g. for server-to-server communication scenarios. Our IdentityModel library has a TokenClient helper class which makes that very easy:

async Task<TokenResponse> GetTokenAsync()
{
    var tokenClient = new TokenClient(
        "http://localhost:5000/connect/token",
        "test",
        "secret");
 
    return await tokenClient.RequestClientCredentialsAsync("api1");
}

…and using the token:

async Task<HttpResponseMessage> CallApi(string accessToken)
{
    var client = new HttpClient();
    client.SetBearerToken(accessToken);
 
    return await client.GetAsync("http://localhost:19806/identity");
}

The full source code can be found here.

Summary
IdentityServer can be a replacement for the Katana authorization server middleware – but as I said it is a different mindset, since IdentityServer is not protocol oriented – but rather focusing on your application architecture. I should mention that there is actually a middleware for ASP.NET 5 that mimics the Katana approach – you can find it here.

In the end it is all about personal preference, and most importantly how comfortable you are with the low level details of the protocols.

There are actually much more samples on how to use IdentityServer in the samples repo. Give it a try.

This entry was posted in .NET Security, ASP.NET, IdentityServer, OAuth, OpenID Connect, OWIN, WebAPI. Bookmark the permalink.

17 Responses to The State of Security in ASP.NET 5 and MVC 6: OAuth 2.0, OpenID Connect and IdentityServer

  1. Pingback: The State of Security in ASP.NET 5 and MVC 6 | leastprivilege.com

  2. SteveM says:

    Thanks, Dominick! Enjoyed listening to you and Scott in Oslo. There was this nagging question of ‘How is this all going to be implemented in ASP.NET5?’ at the back of my mind — answered in this useful post.

  3. Anon says:

    Wonderful article. Is all this production ready ? Currently I am planning to build a rest api using MVC 6 for a Mobile App and a Angular Client. Was not able to find any proper information related to security and then found your article.

  4. Max Smotrov says:

    Hi Dominick! Just found your post while trying to find out how to implement OAuth in ASP.NET 5 Web API. However, the post seems to be out of date or the identity server itself isn’t working with the latest version of the ASP.NET 5 (which is beta7 at the moment). The problem is, when I try to configure the IdentityServer in the Startup.cs I got an error from VS telling me that IApplicationBuilder has no extension method called UseIdentityServer. And this seems to be true, since in the IdentityServer3 source code you have this extension method declared for IAppBuilder (not IApplicationBuilder).

    Could you point me in the right direction please?
    Perhaps I’m just missing something crucial here.

    Thanks in advance!

  5. Greg says:

    Hello Dominic. Please how do you go about using this with Identity 3.0? We have to register the user service somehow and the current implementation of the AspNetIdentityUserService supports Identity 2.0 not 3.0

  6. billgerold says:

    Hi
    Thanks for this information.
    I have been searching for quite a while on how to implement CORS within MVC6 without Identity server?
    The app I am working on will not use (at the current time) anything but local Log ins. Therefor Identity Server is not required.

    Is there any guidance using the latest bits (RC1 Update 1)?

    Any help would be GREATLY appreciated

    • IdentityServer is unrelated to CORS. There is CORS MW in ASP.NET 5

      • billgerold says:

        Thanks for the quick reply.
        I Understand that IS is unrelated to COR. The CORS middle ware seems to have gone through a series of breaking changes when the Release of RC1_Update 1 – I was hoping if you had been able to try the latest bits with CORS.

        Thanks again
        Bill

  7. arghya says:

    OpenIdConnectServerOptions does not provide a RefreshTokenProvider and the UseOAuthAuthorizationServer is no longer supported.

  8. Lukas says:

    Is using UseJwtBearerAuthentication and checking scopes with Authorize[“scope_policy”] considered save in your perspective? Thanks..

  9. Simona Colapicchioni says:

    According to these guys, saving the cookie in the localStorage is a security vulnerability. Using cookies with HttpOnly would be a better solution. In your NDC Oslo session you guys said the exact opposite. Which one of you is right?
    https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage

Leave a comment