6

My ASP.NET 5 (MVC 6 + beta7) web application (MVC + WebAPI) is required to get back an access_token from WebAPI login calls.

So far, from googling, I have created the following code for startup.cs:

app.UseOAuthBearerAuthentication(options => {
    options.AutomaticAuthentication = true;
    options.Audience = "http://localhost:62100/";
    options.Authority = "http://localhost:62100/";
});

My client side is:

var login = function ()
{
    var url = "http://localhost:62100/";
    var data = $("#userData").serialize();
    data = data + "&grant_type=password";
    $.post(url, data)
        .success(saveAccessToken)
        .always(showResponse);
    return false;
};

Is it required to use UseOpenIdConnectServer? If so, how do I use SigningCredentials so that I get a token (e.g. MVC5 ApplicationOAuthProvider)?

Please note that my site is simple demo HTTP site and I do not need any SSL.

Peter
  • 1,674
  • 4
  • 27
  • 44

1 Answers1

6

Is it required to use UseOpenIdConnectServer?

Using AspNet.Security.OpenIdConnect.Server is not "required". You're - of course - free to opt for another server (like IdentityServer) or for a custom solution. Being the main developer behind aspnet-contrib, I'm not really objective, so I'll necessarily suggest going with app.UseOpenIdConnectServer().

If so, how do I use SigningCredentials so that I get a token (e.g. MVC5 ApplicationOAuthProvider)?

When implementing the password and using the default token type, registering a signing key/certificate is not mandatory.

Here's how you can get started:

ASP.NET Core 1.x:

Startup.cs

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication();
    }

    public void Configure(IApplicationBuilder app)
    {
        // Add a new middleware validating the encrypted
        // access tokens issued by the OIDC server.
        app.UseOAuthValidation();

        // Add a new middleware issuing tokens.
        app.UseOpenIdConnectServer(options =>
        {
            options.TokenEndpointPath = "/connect/token";

            // Override OnValidateTokenRequest to skip client authentication.
            options.Provider.OnValidateTokenRequest = context =>
            {
                // Reject the token requests that don't use
                // grant_type=password or grant_type=refresh_token.
                if (!context.Request.IsPasswordGrantType() &&
                    !context.Request.IsRefreshTokenGrantType())
                {
                    context.Reject(
                        error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
                        description: "Only grant_type=password and refresh_token " +
                                     "requests are accepted by this server.");

                    return Task.FromResult(0);
                }

                // Since there's only one application and since it's a public client
                // (i.e a client that cannot keep its credentials private),
                // call Skip() to inform the server the request should be
                // accepted without enforcing client authentication.
                context.Skip();

                return Task.FromResult(0);
            };

            // Override OnHandleTokenRequest to support
            // grant_type=password token requests.
            options.Provider.OnHandleTokenRequest = context =>
            {
                // Only handle grant_type=password token requests and let the
                // OpenID Connect server middleware handle the other grant types.
                if (context.Request.IsPasswordGrantType())
                {
                    // Do your credentials validation here.
                    // Note: you can call Reject() with a message
                    // to indicate that authentication failed.

                    var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
                    identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "[unique id]");

                    // By default, claims are not serialized
                    // in the access and identity tokens.
                    // Use the overload taking a "destinations"
                    // parameter to make sure your claims
                    // are correctly inserted in the appropriate tokens.
                    identity.AddClaim("urn:customclaim", "value",
                        OpenIdConnectConstants.Destinations.AccessToken,
                        OpenIdConnectConstants.Destinations.IdentityToken);

                    var ticket = new AuthenticationTicket(
                        new ClaimsPrincipal(identity),
                        new AuthenticationProperties(),
                        context.Options.AuthenticationScheme);

                    // Call SetScopes with the list of scopes you want to grant
                    // (specify offline_access to issue a refresh token).
                    ticket.SetScopes("profile", "offline_access");

                    context.Validate(ticket);
                }

                return Task.FromResult(0);
            };
        });
    }
}

.csproj

<ItemGroup>
  <PackageReference Include="AspNet.Security.OpenIdConnect.Server" Version="1.0.2" />
</ItemGroup>

ASP.NET Core 2.x:

Startup.cs

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication()
            // Add a new middleware validating the encrypted
            // access tokens issued by the OIDC server.
            .AddOAuthValidation()

            // Add a new middleware issuing tokens.
            .AddOpenIdConnectServer(options =>
            {
                options.TokenEndpointPath = "/connect/token";

                // Override OnValidateTokenRequest to skip client authentication.
                options.Provider.OnValidateTokenRequest = context =>
                {
                    // Reject the token requests that don't use
                    // grant_type=password or grant_type=refresh_token.
                    if (!context.Request.IsPasswordGrantType() &&
                        !context.Request.IsRefreshTokenGrantType())
                    {
                        context.Reject(
                            error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
                            description: "Only grant_type=password and refresh_token " +
                                         "requests are accepted by this server.");

                        return Task.CompletedTask;
                    }

                    // Since there's only one application and since it's a public client
                    // (i.e a client that cannot keep its credentials private),
                    // call Skip() to inform the server the request should be
                    // accepted without enforcing client authentication.
                    context.Skip();

                    return Task.CompletedTask;
                };

                // Override OnHandleTokenRequest to support
                // grant_type=password token requests.
                options.Provider.OnHandleTokenRequest = context =>
                {
                    // Only handle grant_type=password token requests and let the
                    // OpenID Connect server middleware handle the other grant types.
                    if (context.Request.IsPasswordGrantType())
                    {
                        // Do your credentials validation here.
                        // Note: you can call Reject() with a message
                        // to indicate that authentication failed.

                        var identity = new ClaimsIdentity(context.Scheme.Name);
                        identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "[unique id]");

                        // By default, claims are not serialized
                        // in the access and identity tokens.
                        // Use the overload taking a "destinations"
                        // parameter to make sure your claims
                        // are correctly inserted in the appropriate tokens.
                        identity.AddClaim("urn:customclaim", "value",
                            OpenIdConnectConstants.Destinations.AccessToken,
                            OpenIdConnectConstants.Destinations.IdentityToken);

                        var ticket = new AuthenticationTicket(
                            new ClaimsPrincipal(identity),
                            new AuthenticationProperties(),
                            context.Scheme.Name);

                        // Call SetScopes with the list of scopes you want to grant
                        // (specify offline_access to issue a refresh token).
                        ticket.SetScopes("profile", "offline_access");

                        context.Validate(ticket);
                    }

                    return Task.CompletedTask;
                };
            });
    }
}

.csproj

<ItemGroup>
  <PackageReference Include="AspNet.Security.OpenIdConnect.Server" Version="2.0.0-*" />
</ItemGroup>

You can also read this blog post, that explains how to implement the resource owner password grant: http://kevinchalet.com/2016/07/13/creating-your-own-openid-connect-server-with-asos-implementing-the-resource-owner-password-credentials-grant/

Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
  • Thanks very much for your answer. So I could not work out this in Beta 7, correct? When I move to Beta 8, I get so many dependency build errors, so would be more happy if I can figure out in beta 7... – OrionSoftTechnologiesSydney Oct 16 '15 at 04:18
  • You can find a version compatible with ASP.NET beta7 on NuGet.org but it won't offer you the automatic key generation feature. We'll move to beta8 soon and update the corresponding packages on NuGet. In the meantime, you can try our nightly builds. – Kévin Chalet Oct 16 '15 at 05:23
  • Thanks very much for this. When I convert to beta8, i get lot of build error messages from cs files in migration folder. Do you have any suggestion? – OrionSoftTechnologiesSydney Oct 16 '15 at 06:38
  • I think you must remove them (and optionally recreate them if you need to). FYI, I just uploaded the beta3 package of AspNet.Security.OpenIdConnect.Server (for ASP.NET 5 beta8) on NuGet.org: https://www.nuget.org/packages/AspNet.Security.OpenIdConnect.Server/1.0.0-beta3. I've updated my answer to reflect that. – Kévin Chalet Oct 16 '15 at 07:16
  • Thanks very much for your reply. I have recreated the project in Beta8 and now those errors are gone. However I can not Install the Package AspNet.Security.OpenIdConnect.Server beta 3 due to following error message. Can you please specify the location of beta3 of this package? Install-Package : Unable to find package – OrionSoftTechnologiesSydney Oct 19 '15 at 01:38
  • The beta3 version is on NuGet.org (make sure to use "1.0.0-beta3" for the version identifier). – Kévin Chalet Oct 19 '15 at 01:47
  • Yes, I have put beta3, but still getting the same error: dnu : Unable to locate Dependency AspNet.Security.OpenIdConnect.Server >= 1.0.0-beta3 At line:1 char:1 + dnu restore + ~~~~~~~~~~~ + CategoryInfo : NotSpecified: (Unable to locat... >= 1.0.0-beta3:String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError – OrionSoftTechnologiesSydney Oct 19 '15 at 02:01
  • Weird. Could you please post your project.json? – Kévin Chalet Oct 19 '15 at 15:08
  • I've been told NuGet returned strange exceptions due to network issues yesterday. Could you please try again to run `dnu restore`. – Kévin Chalet Oct 19 '15 at 21:19
  • Getting the same error. I can see your beta3 upload @ https://www.nuget.org/packages/AspNet.Security.OpenIdConnect.Server/1.0.0-beta3, but in VS it shows the latest version is beta2. – OrionSoftTechnologiesSydney Oct 19 '15 at 22:16
  • Any suggestion? Still I can see the latest version in visual studio is beta2. – OrionSoftTechnologiesSydney Oct 20 '15 at 11:49
  • No, sorry: it seems you have a problem with your local setup. I suggest opening a new thread on GitHub (https://github.com/aspnet/Home/issues) with as much detail as possible. – Kévin Chalet Oct 20 '15 at 11:57
  • Thanks again. I have created the issue @ https://github.com/aspnet/Home/issues/1011 . – OrionSoftTechnologiesSydney Oct 20 '15 at 22:28
  • I could build using https://www.nuget.org/api/v2. Thanks very much for the support. One last thing, what is the client side changes to make this to work? – OrionSoftTechnologiesSydney Oct 21 '15 at 05:32
  • Current code is: var login = function () { var url = "http://localhost:62100/oauth"; var data = $("#userData").serialize(); data = data + "&grant_type=password"; $.post(url, data) .success(saveAccessToken) .always(showResponse); return false; }; – OrionSoftTechnologiesSydney Oct 21 '15 at 05:34
  • @Pinpoint, thanks for this example. I took a perfunctory stab at it and found I can successfully acquire a token using postman as my client. Presumably I can use my own key by setting options.TokenValidationParameters.IssuerSigningKey and configuration.Options.SigningCredentials? Also if you could point me to a good document to understand what this code is doing I would be very grateful ;o) – 42vogons Oct 24 '15 at 00:15
  • @42vogons yes, you can. Though it's easier to simply set the signing key of your choice in the OIDC server options (using `configuration.UseKey(new RsaSecurityKey(parameter));`) and let the bearer middleware download the public key using the OIDC provider metadata endpoint. There's no documentation yet, but you can take a look at this post: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/129#issuecomment-140038411 – Kévin Chalet Oct 24 '15 at 01:10
  • @Pinpoint, a couple more questions - firstly what would be the recommended way to ingest these tokens using OIDC? I tried [Authorize("Bearer")] on my action but it throws "The AuthorizationPolicy named: 'Bearer' was not found". Also, what would be a better way to ask additional questions about best practices in using OIDC, should I post to your GitHub repo or keep adding comments here? – 42vogons Oct 26 '15 at 23:15
  • You can of course create a new ticket on the GitHub repository, but I guess it's better to simply open a new thread directly on SO, specially if your questions are general enough to be interesting for everyone. – Kévin Chalet Oct 27 '15 at 23:23
  • @Pinpoint, ok cool I'll keep bugging you here ; ) My next question then is how shall I ingest the token, as I tried decorating a controller action with [Authorize("Bearer")] but that only throws an exception. – 42vogons Oct 28 '15 at 11:03
  • To use `[Authorize("Bearer")]`, you need an authorization policy with the same name. Make sure your authorization services/policies are correctly configured with `AddAuthorization(...)`. You're probably looking for `[Authorize(ActiveAuthenticationSchemes = "Bearer")]`, but it's not needed in my sample, since I use `AutomaticAuthentication = true`. – Kévin Chalet Oct 28 '15 at 11:07
  • @Pinpoint, thanks - my understanding the authentication flow is like wrestling an octopus! My objective is to decorate a REST actions on a controller that I want guarded with an access token issued by OIDC. When I use "AddAuthorization(...JwtBearerDefaults.AuthenticationScheme...)" and paint my action with [Authorize("Bearer")] this simply causes OIDC to throw an exception "IDX10804: Unable to retrieve document from: 'http://localhost:50000/.well-known/openid-configuration'". Shall I open another SO question so people can watch you hand-hold me through this? ;o) – 42vogons Oct 28 '15 at 15:03
  • Yes, open a new question ;) – Kévin Chalet Oct 28 '15 at 15:07
  • @Pinpoint, new question posted [here](http://stackoverflow.com/questions/33401936/validating-tokens-issued-by-aspnet-security-openidconnect-server-asp-net-vnext)! – 42vogons Oct 28 '15 at 21:31
  • @Pinpoint, thanks for updating this to reflect the latest rc1-final and OIDC beta 4 changes. When I bring my own codebase up to rc1-final and beta 4 it appears the OpenIdConnectServerOptions.UseKey method is gone/moved. Can you please advise on best practices for me to hook up my own RsaSecurityKey to OIDC beta 4? Thank you. – 42vogons Nov 24 '15 at 15:31
  • @42vogons this extension has been replaced by `options.SigningCredentials.AddKey(new RsaSecurityKey(...))` in beta4. You can find more information on this ticket: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/145#issuecomment-157456444 – Kévin Chalet Nov 24 '15 at 15:42
  • @Pinpoint, thanks. Also another OIDC question for you [here](http://stackoverflow.com/questions/33897111/logging-out-with-aspnet-security-openidconnect-server-asp-net-vnext). – 42vogons Nov 24 '15 at 18:13
  • I updated my answer to use the `SetResources`/`SetScopes` extensions instead of the `resource` parameter. – Kévin Chalet Jan 13 '16 at 01:04
  • Updated to use the ASP.NET Core RTM/ASOS beta6 bits. – Kévin Chalet Jul 06 '16 at 10:24