2

I had an app in .NET Framework in which I implemented OAuthAuthorizationServer. Now I want to upgrade my app to .NET Core 2.1, so I did some R&D and decided to use ASOS. Now the issue is I have implemented ASOS and it is working fine but I have some chunks that I can't figure out how to convert.

private Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
    var identity = new ClaimsIdentity(new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType),
        context.Scope.Select(x => new Claim("claim", x)));

    context.Validated(identity);

    return Task.FromResult(0);
}

private Task GrantClientCredetails(OAuthGrantClientCredentialsContext context)
{
    var identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType),
        context.Scope.Select(x => new Claim("claim", x)));

    context.Validated(identity);

    return Task.FromResult(0);
}


private readonly ConcurrentDictionary<string, string> _authenticationCodes =
    new ConcurrentDictionary<string, string>(StringComparer.Ordinal);

private void CreateAuthenticationCode(AuthenticationTokenCreateContext context)
{
    context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
    _authenticationCodes[context.Token] = context.SerializeTicket();
}

private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)
{
    string value;
    if (_authenticationCodes.TryRemove(context.Token, out value))
    {
        context.DeserializeTicket(value);
    }
}

private void CreateRefreshToken(AuthenticationTokenCreateContext context)
{
    context.SetToken(context.SerializeTicket());
}

private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context)
{
    context.DeserializeTicket(context.Token);
}

Now I have couple of question:

  • Client Credentials and Resource owner password grant types are two different grant types so how can we differentiate in them using ASOS?
  • GrantResourceOwnerCredentials takes OAuthGrantResourceOwnerCredentialsContext as a param and GrantClientCredentials takes OAuthGrantClientCredentialsContext as a param. Both these contexts contains scope which is not available in ASOS.
  • How can I serialize and deserialize access and refresh tokens like I was doing OAuthAuthorizationProvider?
  • How do we handle refresh tokens in ASOS? I can see refresh tokens in response but I haven't write any logic for refresh token my self.
poke
  • 369,085
  • 72
  • 557
  • 602
Ask
  • 3,076
  • 6
  • 30
  • 63

1 Answers1

4

Client Credentials and Resource owner password grant types are two different grant types so how can we differentiate in them using ASOS?

public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
    if (context.Request.IsClientCredentialsGrantType())
    {
        // ...
    }

    else if (context.Request.IsPasswordGrantType())
    {
        // ...
    }

    else
    {
        throw new NotSupportedException();
    }
}

GrantResourceOwnerCredentials takes OAuthGrantResourceOwnerCredentialsContext as a param and GrantClientCredetails takes OAuthGrantClientCredentialsContext as a param. Both these contexts contains scope which is not available in ASOS

public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
    var scopes = context.Request.GetScopes();

    // ...
}

How can I serialize and deserialize access and refresh tokens like I was doing OAuthAUthorizationProvider?

By using the OnSerializeAccessToken/OnDeserializeAccessToken and OnSerializeRefreshToken/OnDeserializeRefreshToken events.

How do we handle refresh tokens in ASOS? I can see refresh tokens in response but I haven't write any logic for refresh token my self.

Unlike Katana's OAuth server middleware, ASOS provides default logic for generating authorization codes and refresh tokens. If you want to use implement things like token revocation, you can do that in the events I mentioned. Read AspNet.Security.OpenIdConnect.Server. Refresh tokens for more information.

Here's an example that returns GUID refresh tokens and stores the associated (encrypted) payload in a database:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace AuthorizationServer
{
    public class MyToken
    {
        public string Id { get; set; }
        public string Payload { get; set; }
    }

    public class MyDbContext : DbContext
    {
        public MyDbContext(DbContextOptions<MyDbContext> options)
            : base(options) { }

        public DbSet<MyToken> Tokens { get; set; }
    }

    public class MyProvider : OpenIdConnectServerProvider
    {
        private readonly MyDbContext _database;

        public MyProvider(MyDbContext database)
        {
            _database = database;
        }

        public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
        {
            if (!context.Request.IsPasswordGrantType() && !context.Request.IsRefreshTokenGrantType())
            {
                context.Reject(error: OpenIdConnectConstants.Errors.UnsupportedGrantType);
            }
            else
            {
                // Don't enforce client authentication.
                context.Skip();
            }
            return Task.CompletedTask;
        }

        public override async Task HandleTokenRequest(HandleTokenRequestContext context)
        {
            if (context.Request.IsPasswordGrantType())
            {
                if (context.Request.Username == "bob" && context.Request.Password == "bob")
                {
                    var identity = new ClaimsIdentity(context.Scheme.Name);
                    identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Subject, "Bob"));

                    var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), identity.AuthenticationType);
                    ticket.SetScopes(OpenIdConnectConstants.Scopes.OfflineAccess);

                    context.Validate(ticket);
                }
                else
                {
                    context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidGrant,
                        description: "The username/password couple is invalid.");
                }
            }
            else
            {
                var token = await _database.Tokens.FindAsync(context.Request.RefreshToken);
                _database.Tokens.Remove(token);
                await _database.SaveChangesAsync();

                context.Validate(context.Ticket);
            }
        }

        public override async Task SerializeRefreshToken(SerializeRefreshTokenContext context)
        {
            context.RefreshToken = Guid.NewGuid().ToString();
            _database.Tokens.Add(new MyToken
            {
                Id = context.RefreshToken,
                Payload = context.Options.RefreshTokenFormat.Protect(context.Ticket)
            });
            await _database.SaveChangesAsync();
        }

        public override async Task DeserializeRefreshToken(DeserializeRefreshTokenContext context)
        {
            context.HandleDeserialization();
            var token = await _database.Tokens.FindAsync(context.RefreshToken);
            if (token == null)
            {
                return;
            }

            context.Ticket = context.Options.RefreshTokenFormat.Unprotect(token.Payload);
        }
    }

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<MyDbContext>(options =>
            {
                options.UseInMemoryDatabase(nameof(MyDbContext));
            });

            services.AddAuthentication()
                .AddOpenIdConnectServer(options =>
                {
                    options.TokenEndpointPath = "/token";
                    options.ProviderType = typeof(MyProvider);
                    options.AllowInsecureHttp = true;
                })

                .AddOAuthValidation();

            services.AddMvc();

            services.AddScoped<MyProvider>();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseAuthentication();

            app.UseMvcWithDefaultRoute();
        }
    }
}
Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
  • thanks for so much clarification. I have one more question. If you see the code snippet in my question I used context.SetToken(Guid...) in CreateAuthenticationCode() method and Serializing it and adding it in Concurrent Dict. So can I set my access token in the same way? Something Like context.SetToken(Guid....) in options.Provider.OnSerializeAccessToken? – Ask Aug 06 '18 at 06:19
  • @Ask it works exactly the same way. Set `context.AccessToken` with your custom access token and ASOS will use it instead of using ASP.NET Core Data Protection. – Kévin Chalet Aug 06 '18 at 11:01
  • Thanks for the answer. I want to do another thing using ASOS in one of my other project. I have asked a question on SO https://stackoverflow.com/questions/51705862/ssl-authentication-with-aspnet-security-openidconnect-server. Can you kindly answer that? – Ask Aug 06 '18 at 11:04