2

I would like to build a authorization service using gRPC under .Net Code. In order to do that, I need to pass a System.Security.Claims.ClaimsPrincipal object as a request argument from caller to the server so the server can use it to authorize the caller. But I don't know how to that - how can I define a .proto for a class that is a standard library. What am I supposed to do?

Alexu
  • 1,015
  • 2
  • 12
  • 32

1 Answers1

1

I'm doing the same, using protobuf-net grpc libraries. As many of the Identity/Security classes (if you are using them) are from Microsoft, you'll need to expose their members for serialization; you can use:

RuntimeTypeModel.Default.Add(typeof(SignInResult), false).Add(
            nameof(SignInResult.Succeeded),
            nameof(SignInResult.IsLockedOut),
            nameof(SignInResult.IsNotAllowed),
            nameof(SignInResult.RequiresTwoFactor)
        );

and list the members that need to be exposed over gRpc. As for ClaimsPrincipal, specifically, that is what I'm currently trying to implement. For Claims, i'm using a surrogate class:

RuntimeTypeModel.Default.Add(typeof(Claim), true).SetSurrogate(typeof(ClaimSurrogate));

public class ClaimSurrogate
{
    [DataMember, ProtoMember(1)]
    public string Type { get; set; }
    [DataMember, ProtoMember(2)]
    public ClaimsIdentity Subject { get; set; }
    [DataMember, ProtoMember(3)]
    public IDictionary<string, string> Properties { get; set; }
    [DataMember, ProtoMember(4)]
    public string OriginalIssuer { get; set; }
    [DataMember, ProtoMember(5)]
    public string Issuer { get; set; }
    [DataMember, ProtoMember(6)]
    public string ValueType { get; set; }
    [DataMember, ProtoMember(7)]
    public string Value { get; set; }

    public static implicit operator ClaimSurrogate(Claim claim)
    {
        if (claim == null)
            return null;
        return new ClaimSurrogate()
        {
            Type = claim.Type,
            Subject = claim.Subject,
            Properties = claim.Properties,
            OriginalIssuer = claim.OriginalIssuer,
            Issuer = claim.Issuer,
            ValueType = claim.ValueType,
            Value = claim.Value
        };
    }

    public static implicit operator Claim(ClaimSurrogate surrogate)
    {
        if (surrogate == null)
            return null;
        return new Claim(surrogate.Type, surrogate.Value, surrogate.ValueType, surrogate.Issuer, surrogate.OriginalIssuer, surrogate.Subject);
    }

}

And I'm assuming that ClaimsPrincipal can be done the same way, but, I'm having trouble with it. That is how I came across your question... Actually, by, trying to provide an answer...Literally, I just realized what I overlooked, I need to also set up a surrogate for the ClaimsIdentity

So far, I've needed surrogates for 'third' party classes that have get; only properties. ClaimsPrincipal has these types of properties, and so does ClaimsIdentity (as does Claim). I'll update/comment if ClaimsIdentitySurrogate does the trick

Updates:

Yes, it can be done. Surrogates, like the example above, will be needed for ClaimsIdentity and IIdentity. These classes are used as members/properties within ClaimsPrincipal. ClaimsIdentity: you can mix up the SetSurrogate and the Add(nameof(...)) as it has get onlies and get/sets (get/sets go in the Add portion). Do not include the Actor in the ClaimsIdentity surrogate as it will create a never ending loop in your service's startup. If you do include it, make sure it is not a DataMember/Protomember. And (private) set it in the surrogate operator. Same same with Claims.

Essentially, any surrogates with members that reference the parent class, or that of a another type with a surrogate that references this parent type, will create a circular reference and error out your service on startup.

IIdentity: This is a simple one, just RuntimeTypeModel.Default.Add(typeof(IIdentity), false).

Lastly (I posted this update when I thought I had it, but, amidst all the UT tests and changes, etc, I posted a bit early; after making a breaking change on the ClaimPrincipal surrogate class)....

You'll want an IIdentity dummy class that will be used in your ClaimPrincipal surrogate, instead of the IIdentity Identity {get;set;}. This dummy class should inherit from IIdentity, e.g.

[DataContract]
public class IIdentityFraud : System.Security.Principal.IIdentity

And within your surrogate's implicit operator:

IIdentityFraud identityfraud = null;
if (claimPrincipal.Identity != null)
{
     identityfraud = new IIdentityFraud(claimPrincipal.Identity.AuthenticationType, claimPrincipal.Identity.Name, claimPrincipal.Identity.IsAuthenticated);
}

Updates (11/05/2021):

[DataContract]
    public class ClaimsPrincipalSurrogate
    {
        [DataMember, ProtoMember(1)]
        public IIdentityFraud Identity { get; set; }
        [DataMember, ProtoMember(2)]
        public IEnumerable<ClaimsIdentity> Identities { get; set; }
        [DataMember, ProtoMember(3)]
        public IEnumerable<Claim> Claims { get; set; }

        public static implicit operator ClaimsPrincipalSurrogate(ClaimsPrincipal claimPrincipal)
        {
            if (claimPrincipal == null)
            {
                return null;
            }
            else
            {
                IIdentityFraud identityfraud = null;
                if (claimPrincipal.Identity != null)
                {
                    identityfraud = new IIdentityFraud(claimPrincipal.Identity.AuthenticationType, claimPrincipal.Identity.Name, claimPrincipal.Identity.IsAuthenticated);
                }

                return new ClaimsPrincipalSurrogate()
                {
                    Identity = identityfraud, // (System.Security.Principal.IIdentity)identityfraud,
                    Identities = claimPrincipal.Identities,
                    Claims = claimPrincipal.Claims
                };
            }
        }

        public static implicit operator ClaimsPrincipal(ClaimsPrincipalSurrogate surrogate)
        {
            if (surrogate == null)
                return null;

            if (surrogate.Identities != null && surrogate.Identities.Any() == true)
            {
                return new ClaimsPrincipal(surrogate.Identities);
            }
            else if (surrogate.Identity != null)
            {
                return new ClaimsPrincipal(surrogate.Identity);
            }
            return new ClaimsPrincipal();
        }

    }

    [DataContract]
    public class ClaimsIdentitySurrogate
    {
        [DataMember, ProtoMember(1)]
        public string AuthenticationType { get; set; }
        [DataMember, ProtoMember(2)]
        public string Name { get; set; }
        //[DataMember, ProtoMember(3)]
        //public string Label { get; set; }
        [DataMember, ProtoMember(4)]
        public bool IsAuthenticated { get; set; }
        [DataMember, ProtoMember(5)]
        public IEnumerable<Claim> Claims { get; private set; }
        //[DataMember, ProtoMember(6)]
        //public object BootstrapContext { get; set; }
        //[DataMember, ProtoMember(7)]
        public ClaimsIdentity Actor { get; private set; }
        [DataMember, ProtoMember(8)]
        public string RoleClaimType { get; set; }
        [DataMember, ProtoMember(9)]
        public string NameClaimType { get; set; }

        public static implicit operator ClaimsIdentitySurrogate(ClaimsIdentity claimIdentity)
        {
            if (claimIdentity == null)
                return null;
            return new ClaimsIdentitySurrogate()
            {
                AuthenticationType = claimIdentity.AuthenticationType,
                Name = claimIdentity.Name,
                //Label = claimIdentity.Label,
                IsAuthenticated = claimIdentity.IsAuthenticated,
                Claims = claimIdentity.Claims,
                //BootstrapContext = claimIdentity.AuthenticationType,
                Actor = claimIdentity.Actor,
                RoleClaimType = claimIdentity.RoleClaimType,
                NameClaimType = claimIdentity.NameClaimType
            };
        }

        public static implicit operator ClaimsIdentity(ClaimsIdentitySurrogate surrogate)
        {
            if (surrogate == null)
            {
                return null;
            }

            if (surrogate.Claims?.Any() == true)
            {
                return new ClaimsIdentity(surrogate.Claims, surrogate.AuthenticationType);
            }
            else
            {
                return new ClaimsIdentity(surrogate.AuthenticationType, surrogate.NameClaimType, surrogate.RoleClaimType);
            }
        }
    }


[DataContract]
public class IIdentityFraud : System.Security.Principal.IIdentity
{
    [DataMember, ProtoMember(1)]
    public string AuthenticationType { get; private set; }
    [DataMember, ProtoMember(2)]
    public string Name { get; private set; }
    [DataMember, ProtoMember(3)]
    public bool IsAuthenticated { get; private set; }

    public IIdentityFraud() { }
    public IIdentityFraud(string authenticationType, string name, bool isAuthenticated)
    {
        this.AuthenticationType = authenticationType;
        this.Name = name;
        this.IsAuthenticated = isAuthenticated;
    }
}


[DataContract] //don't know if this is really needed. Too involved in testing out the rest of it and have yet to come back to this.
public class IIdentitySurrogate : System.Security.Principal.IIdentity
{
    [DataMember, ProtoMember(1)]
    public string AuthenticationType { get; set; }
    [DataMember, ProtoMember(2)]
    public string Name { get; set; }
    [DataMember, ProtoMember(3)]
    public bool IsAuthenticated { get; set; }

    public static implicit operator IIdentitySurrogate(IIdentityFraud iidentity)
    {
        if (iidentity == null)
            return null;
        return new IIdentitySurrogate()
        {
            AuthenticationType = iidentity.AuthenticationType,
            Name = iidentity.Name,
            IsAuthenticated = iidentity.IsAuthenticated
        };
    }

    public static implicit operator IIdentityFraud(IIdentitySurrogate surrogate)
    {
        if (surrogate == null)
            return null;

        return new IIdentityFraud(surrogate.AuthenticationType, surrogate.Name, surrogate.IsAuthenticated);
    }

}

More of what is executed on startups:

#region ClaimsIdentity
            RuntimeTypeModel.Default.Add(typeof(ClaimsIdentity), true).Add(
                nameof(ClaimsIdentity.Label),
                nameof(ClaimsIdentity.BootstrapContext),
                nameof(ClaimsIdentity.Actor)
            ).SetSurrogate(typeof(ClaimsIdentitySurrogate));
            #endregion ClaimsIdentity

            #region ClaimsPrincipal
            RuntimeTypeModel.Default.Add(typeof(ClaimsPrincipal), true).SetSurrogate(typeof(ClaimsPrincipalSurrogate));
            #endregion ClaimsPrincipal

            #region IIdentity
            RuntimeTypeModel.Default.Add(typeof(IIdentity), true);
            #endregion IIdentity
fafafooey
  • 181
  • 1
  • 2
  • 13
  • Thanks fafafooey for the input. Please keep me updated with your experiments. I will accept your answer once I tested your solution. – Alexu Nov 01 '21 at 15:24
  • @Alexu it can be done, you'll need to set up surrogates for ClaimsPrincipal and ClaimsIdentity (if needed) – fafafooey Nov 03 '21 at 01:46
  • Yes, I tried that before, but wasn't able to get all the way through. Part of the reason was I couldn't figure out how to handle those virtual properties and funcs, such as "public virtual string? Name { get; }" and "public static Func ClaimsPrincipalSelector". – Alexu Nov 04 '21 at 14:19
  • Maybe those are not needed? Do you mind to post your ClaimsPrincipalSurrogate and ClaimsIdentitySurrogate classes? – Alexu Nov 04 '21 at 14:26
  • @Alexu see updates – fafafooey Nov 05 '21 at 15:15
  • Thanks @fafafooey! I am trying the code you posted right now. Just want to confirm - are these code working for you already? – Alexu Nov 08 '21 at 14:38
  • @Alexu yes, it is working. – fafafooey Nov 09 '21 at 15:41
  • I am still in the process of testing the code out. But I trust you and accepted your answer. I will let you know if there is issues. Thanks again! – Alexu Nov 10 '21 at 14:33
  • 1
    I am still unable to get the code working. I use all your classes and added RuntimeTypeModel.Default.Add(typeof(typeOne), false).SetSurrogate(typeof(typeOneSurrogate)); for all 4 types on both client and server side static constructors. When I try it, I got an exception that says "System.InvalidOperationException: No serializer defined for type: System.Security.Claims.ClaimsIdentity". I thought it's interesting that it only complaint about ClaimsIdentity, not ClaimsPrincipal. But why am I getting this? – Alexu Nov 12 '21 at 21:12
  • @Alexu Sorry for the late reply. Are you still having issues? If so, I'd need to see what you have in order to better help out. Also, is this occurring on startup of the service, or on certain methods? – fafafooey Nov 22 '21 at 22:48
  • fafafooey, thanks for getting back to me on this! Yes, I still have this issue, when I invoke a function passing ClaimsPrincipalSurrogate parameter. But I am now moving along with a workaround - I realized that I don't need to pass the entire object over. All I need are some pieces in the object, such user ID, etc. So what I am doing are just collecting all those pieces I need and pass them over in a totally new type. I might run into situations where I will need the ClaimsPrincipal object down the road, but I am ok with what I need now. – Alexu Nov 24 '21 at 16:40
  • @Alexu I’ll double check my claims/claims principal implementation. Im wondering if there were a small adjustment to it prior to posting, that I overlooked. Currently, the service is running the user manager, signinmanager (majority are not needed), and persistent grant store. There is a client side implementation that is using it without fail. The sign in manager is a hybrid, using the usermanager service in conjunction with base/httpcontext methods. Only one method from it needs to be overridden. – fafafooey Nov 26 '21 at 03:39
  • @Alexu , I just went through the code again. It is the same as what was posted. Did you implement in the static objects, functions/actions that were a part of the ClaimsIdentity object? – fafafooey Nov 30 '21 at 19:09
  • thank you so much for your help. Are you saying that those surrogate classes need to be static? I do not have them defined as static classes. I just set the Surrogate objects in statics constructors on both client side and server side. Let me know if you want me to copy my code and send them to you. – Alexu Nov 30 '21 at 20:58
  • @Alexu Oh no, I was saying that you should omit the static members in the surrogate class. For example: in ClaimsPrincipal there are: public static ClaimsPrincipal Current { get; } public static Func ClaimsPrincipalSelector { get; set; } public static Func, ClaimsIdentity> PrimaryIdentitySelector { get; set; } Do not include these in your surrogate class. – fafafooey Dec 01 '21 at 00:07