expose AuthContext in C#
diff --git a/src/csharp/Grpc.Core.Tests/AuthContextTest.cs b/src/csharp/Grpc.Core.Tests/AuthContextTest.cs
new file mode 100644
index 0000000..f5fa469
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/AuthContextTest.cs
@@ -0,0 +1,86 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using Grpc.Core;
+using System.Linq;
+
+namespace Grpc.Core.Tests
+{
+    public class AuthContextTest
+    {
+        [Test]
+        public void EmptyContext()
+        {
+            var context = new AuthContext(null, new Dictionary<string, List<AuthProperty>>());
+            Assert.IsFalse(context.IsPeerAuthenticated);
+            Assert.IsNull(context.PeerIdentityPropertyName);
+            Assert.AreEqual(0, context.PeerIdentity.Count());
+            Assert.AreEqual(0, context.Properties.Count());
+            Assert.AreEqual(0, context.FindPropertiesByName("nonexistent").Count());
+        }
+
+        [Test]
+        public void AuthenticatedContext()
+        {
+            var property1 = AuthProperty.Create("abc", new byte[] { 68, 69, 70 });
+            var context = new AuthContext("some_identity", new Dictionary<string, List<AuthProperty>>
+            {
+                {"some_identity", new List<AuthProperty> {property1}}
+            });
+            Assert.IsTrue(context.IsPeerAuthenticated);
+            Assert.AreEqual("some_identity", context.PeerIdentityPropertyName);
+            Assert.AreEqual(1, context.PeerIdentity.Count());
+        }
+
+        [Test]
+        public void FindPropertiesByName()
+        {
+            var property1 = AuthProperty.Create("abc", new byte[] {68, 69, 70});
+            var property2 = AuthProperty.Create("abc", new byte[] {71, 72, 73 });
+            var property3 = AuthProperty.Create("abc", new byte[] {});
+            var context = new AuthContext(null, new Dictionary<string, List<AuthProperty>>
+            {
+                {"existent", new List<AuthProperty> {property1, property2}},
+                {"foobar", new List<AuthProperty> {property3}},
+            });
+            Assert.AreEqual(3, context.Properties.Count());
+            Assert.AreEqual(0, context.FindPropertiesByName("nonexistent").Count());
+
+            var existentProperties = new List<AuthProperty>(context.FindPropertiesByName("existent"));
+            Assert.AreEqual(2, existentProperties.Count);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/AuthPropertyTest.cs b/src/csharp/Grpc.Core.Tests/AuthPropertyTest.cs
new file mode 100644
index 0000000..745191b
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/AuthPropertyTest.cs
@@ -0,0 +1,82 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+    public class AuthPropertyTest
+    {
+        [Test]
+        public void Create_NameIsNotNull()
+        {
+            Assert.Throws(typeof(ArgumentNullException), () => AuthProperty.Create(null, new byte[0]));
+            Assert.Throws(typeof(ArgumentNullException), () => AuthProperty.CreateUnsafe(null, new byte[0]));
+        }
+
+        [Test]
+        public void Create_ValueIsNotNull()
+        {
+            Assert.Throws(typeof(ArgumentNullException), () => AuthProperty.Create("abc", null));
+            Assert.Throws(typeof(ArgumentNullException), () => AuthProperty.CreateUnsafe("abc", null));
+        }
+
+        [Test]
+        public void Create()
+        {
+            var valueBytes = new byte[] { 68, 69, 70 };
+            var authProperty = AuthProperty.Create("abc", valueBytes);
+
+            Assert.AreEqual("abc", authProperty.Name);
+            Assert.AreNotSame(valueBytes, authProperty.ValueBytesUnsafe);
+            CollectionAssert.AreEqual(valueBytes, authProperty.ValueBytes);
+            CollectionAssert.AreEqual(valueBytes, authProperty.ValueBytesUnsafe);
+            Assert.AreEqual("DEF", authProperty.Value);
+        }
+
+        [Test]
+        public void CreateUnsafe()
+        {
+            var valueBytes = new byte[] { 68, 69, 70 };
+            var authProperty = AuthProperty.CreateUnsafe("abc", valueBytes);
+
+            Assert.AreEqual("abc", authProperty.Name);
+            Assert.AreSame(valueBytes, authProperty.ValueBytesUnsafe);
+            Assert.AreNotSame(valueBytes, authProperty.ValueBytes);
+            CollectionAssert.AreEqual(valueBytes, authProperty.ValueBytes);
+            CollectionAssert.AreEqual(valueBytes, authProperty.ValueBytesUnsafe);
+            Assert.AreEqual("DEF", authProperty.Value);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
index 6bf9756..3a99107 100644
--- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
@@ -376,6 +376,18 @@
         }
 
         [Test]
+        public void ServerCallContext_AuthContextNotPopulated()
+        {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                Assert.IsFalse(context.AuthContext.IsPeerAuthenticated);
+                Assert.AreEqual(0, context.AuthContext.Properties.Count());
+                return "PASS";
+            });
+            Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+        }
+
+        [Test]
         public async Task Channel_WaitForStateChangedAsync()
         {
             helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index 646effe..a5e60e7 100644
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -81,6 +81,8 @@
     <Compile Include="ShutdownHookPendingCallTest.cs" />
     <Compile Include="ShutdownHookClientTest.cs" />
     <Compile Include="AppDomainUnloadTest.cs" />
+    <Compile Include="AuthContextTest.cs" />
+    <Compile Include="AuthPropertyTest.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
diff --git a/src/csharp/Grpc.Core/AuthContext.cs b/src/csharp/Grpc.Core/AuthContext.cs
new file mode 100644
index 0000000..340b220
--- /dev/null
+++ b/src/csharp/Grpc.Core/AuthContext.cs
@@ -0,0 +1,128 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Authentication context for a call.
+    /// AuthContext is the only reliable source of truth when it comes to authenticating calls.
+    /// Using any other call/context properties for authentication purposes is wrong and inherently unsafe.
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// </summary>
+    public class AuthContext
+    {
+        string peerIdentityPropertyName;
+        Dictionary<string, List<AuthProperty>> properties;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="T:Grpc.Core.AuthContext"/> class.
+        /// </summary>
+        /// <param name="peerIdentityPropertyName">Peer identity property name.</param>
+        /// <param name="properties">Multimap of auth properties by name.</param>
+        internal AuthContext(string peerIdentityPropertyName, Dictionary<string, List<AuthProperty>> properties)
+        {
+            this.peerIdentityPropertyName = peerIdentityPropertyName;
+            this.properties = GrpcPreconditions.CheckNotNull(properties);
+        }
+
+        /// <summary>
+        /// Returns <c>true</c> if the peer is authenticated.
+        /// </summary>
+        public bool IsPeerAuthenticated
+        {
+            get
+            {
+                return peerIdentityPropertyName != null;
+            }
+        }
+
+        /// <summary>
+        /// Gets the name of the property that indicates the peer identity. Returns <c>null</c>
+        /// if the peer is not authenticated.
+        /// </summary>
+        public string PeerIdentityPropertyName
+        {
+            get
+            {
+                return peerIdentityPropertyName;
+            }
+        }
+
+        /// <summary>
+        /// Gets properties that represent the peer identity (there can be more than one). Returns an empty collection
+        /// if the peer is not authenticated.
+        /// </summary>
+        public IEnumerable<AuthProperty> PeerIdentity
+        {
+            get
+            {
+                if (peerIdentityPropertyName == null)
+                {
+                    return Enumerable.Empty<AuthProperty>();
+                }
+                return properties[peerIdentityPropertyName];
+            }
+        }
+
+        /// <summary>
+        /// Gets the auth properties of this context.
+        /// </summary>
+        public IEnumerable<AuthProperty> Properties
+        {
+            get
+            {
+                return properties.Values.SelectMany(v => v);
+            }
+        }
+
+        /// <summary>
+        /// Returns the auth properties with given name (there can be more than one).
+        /// If no properties of given name exist, an empty collection will be returned.
+        /// </summary>
+        public IEnumerable<AuthProperty> FindPropertiesByName(string propertyName)
+        {
+            List<AuthProperty> result;
+            if (!properties.TryGetValue(propertyName, out result))
+            {
+                return Enumerable.Empty<AuthProperty>();
+            }
+            return result;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/AuthProperty.cs b/src/csharp/Grpc.Core/AuthProperty.cs
new file mode 100644
index 0000000..c7a132b
--- /dev/null
+++ b/src/csharp/Grpc.Core/AuthProperty.cs
@@ -0,0 +1,126 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// A property of an <see cref="AuthContext"/>.
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// </summary>
+    public class AuthProperty
+    {
+        string name;
+        byte[] valueBytes;
+        Lazy<string> value;
+
+        private AuthProperty(string name, byte[] valueBytes)
+        {
+            this.name = GrpcPreconditions.CheckNotNull(name);
+            this.valueBytes = GrpcPreconditions.CheckNotNull(valueBytes);
+            this.value = new Lazy<string>(() => MarshalUtils.GetStringUTF8(this.valueBytes));
+        }
+
+        /// <summary>
+        /// Gets the name of the property.
+        /// </summary>
+        public string Name
+        {
+            get
+            {
+                return name;
+            }
+        }
+
+        /// <summary>
+        /// Gets the string value of the property.
+        /// </summary>
+        public string Value
+        {
+            get
+            {
+                return value.Value;
+            }
+        }
+
+        /// <summary>
+        /// Gets the binary value of the property.
+        /// </summary>
+        public byte[] ValueBytes
+        {
+            get
+            {
+                var valueCopy = new byte[valueBytes.Length];
+                Buffer.BlockCopy(valueBytes, 0, valueCopy, 0, valueBytes.Length);
+                return valueCopy;
+            }
+        }
+
+        /// <summary>
+        /// Creates an instance of <c>AuthProperty</c>.
+        /// </summary>
+        /// <param name="name">the name</param>
+        /// <param name="valueBytes">the binary value of the property</param>
+        public static AuthProperty Create(string name, byte[] valueBytes)
+        {
+            GrpcPreconditions.CheckNotNull(valueBytes);
+            var valueCopy = new byte[valueBytes.Length];
+            Buffer.BlockCopy(valueBytes, 0, valueCopy, 0, valueBytes.Length);
+            return new AuthProperty(name, valueCopy);
+        }
+
+        /// <summary>
+        /// Gets the binary value of the property (without making a defensive copy).
+        /// </summary>
+        internal byte[] ValueBytesUnsafe
+        {
+            get
+            {
+                return valueBytes;
+            }
+        }
+
+        /// <summary>
+        /// Creates and instance of <c>AuthProperty</c> without making a defensive copy of <c>valueBytes</c>.
+        /// </summary>
+        internal static AuthProperty CreateUnsafe(string name, byte[] valueBytes)
+        {
+            return new AuthProperty(name, valueBytes);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index 23e1ddc..d6d8dfa 100644
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -131,6 +131,10 @@
     <Compile Include="Internal\RequestCallContextSafeHandle.cs" />
     <Compile Include="Utils\TaskUtils.cs" />
     <Compile Include="Internal\CallFlags.cs" />
+    <Compile Include="AuthContext.cs" />
+    <Compile Include="Internal\AuthContextSafeHandle.cs" />
+    <Compile Include="Internal\MarshalUtils.cs" />
+    <Compile Include="AuthProperty.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="Grpc.Core.project.json" />
diff --git a/src/csharp/Grpc.Core/Internal/AuthContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/AuthContextSafeHandle.cs
new file mode 100644
index 0000000..59e33a0
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/AuthContextSafeHandle.cs
@@ -0,0 +1,119 @@
+#region Copyright notice and license
+
+// Copyright 2017, Google Inc.
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+// 
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+using Grpc.Core;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core.Internal
+{
+    /// <summary>
+    /// grpc_auth_context
+    /// </summary>
+    internal class AuthContextSafeHandle : SafeHandleZeroIsInvalid
+    {
+        static readonly NativeMethods Native = NativeMethods.Get();
+
+        private AuthContextSafeHandle()
+        {
+        }
+
+        /// <summary>
+        /// Copies contents of the native auth context into a new <c>AuthContext</c> instance.
+        /// </summary>
+        public AuthContext ToAuthContext()
+        {
+            if (IsInvalid)
+            {
+                return new AuthContext(null, new Dictionary<string, List<AuthProperty>>());
+            }
+
+            var peerIdentityPropertyName = Marshal.PtrToStringAnsi(Native.grpcsharp_auth_context_peer_identity_property_name(this));
+
+            var propertiesDict = new Dictionary<string, List<AuthProperty>>();
+
+            var it = Native.grpcsharp_auth_context_property_iterator(this);
+            IntPtr authPropertyPtr = IntPtr.Zero;
+            while ((authPropertyPtr = Native.grpcsharp_auth_property_iterator_next(ref it)) != IntPtr.Zero)
+            {
+                var authProperty = PtrToAuthProperty(authPropertyPtr);
+
+                if (!propertiesDict.ContainsKey(authProperty.Name))
+                {
+                    propertiesDict[authProperty.Name] = new List<AuthProperty>();
+                }
+                propertiesDict[authProperty.Name].Add(authProperty);
+            }
+
+            return new AuthContext(peerIdentityPropertyName, propertiesDict);
+        }
+
+        protected override bool ReleaseHandle()
+        {
+            Native.grpcsharp_auth_context_release(handle);
+            return true;
+        }
+
+        private AuthProperty PtrToAuthProperty(IntPtr authPropertyPtr)
+        {
+            var nativeAuthProperty = (NativeAuthProperty) Marshal.PtrToStructure(authPropertyPtr, typeof(NativeAuthProperty));
+            var name = Marshal.PtrToStringAnsi(nativeAuthProperty.Name);
+            var valueBytes = new byte[(int) nativeAuthProperty.ValueLength];
+            Marshal.Copy(nativeAuthProperty.Value, valueBytes, 0, (int)nativeAuthProperty.ValueLength);
+            return AuthProperty.CreateUnsafe(name, valueBytes);
+        }
+
+        /// <summary>
+        /// grpc_auth_property
+        /// </summary>
+        internal struct NativeAuthProperty
+        {
+            public IntPtr Name;
+            public IntPtr Value;
+            public UIntPtr ValueLength;
+        }
+
+        /// <summary>
+        /// grpc_auth_property_iterator
+        /// </summary>
+        internal struct NativeAuthPropertyIterator
+        {
+            public IntPtr AuthContext;
+            public UIntPtr Index;
+            public IntPtr Name;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
index efae149..6dee6d8 100644
--- a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
@@ -43,7 +43,6 @@
     /// </summary>
     internal class BatchContextSafeHandle : SafeHandleZeroIsInvalid
     {
-        static readonly Encoding EncodingUTF8 = System.Text.Encoding.UTF8;
         static readonly NativeMethods Native = NativeMethods.Get();
 
         private BatchContextSafeHandle()
@@ -75,7 +74,7 @@
         {
             UIntPtr detailsLength;
             IntPtr detailsPtr = Native.grpcsharp_batch_context_recv_status_on_client_details(this, out detailsLength);
-            string details = PtrToStringUtf8(detailsPtr, (int) detailsLength.ToUInt32());
+            string details = MarshalUtils.PtrToStringUTF8(detailsPtr, (int) detailsLength.ToUInt32());
             var status = new Status(Native.grpcsharp_batch_context_recv_status_on_client_status(this), details);
 
             IntPtr metadataArrayPtr = Native.grpcsharp_batch_context_recv_status_on_client_trailing_metadata(this);
@@ -108,12 +107,5 @@
             Native.grpcsharp_batch_context_destroy(handle);
             return true;
         }
-
-        string PtrToStringUtf8(IntPtr ptr, int len)
-        {
-            var bytes = new byte[len];
-            Marshal.Copy(ptr, bytes, 0, len);
-            return EncodingUTF8.GetString(bytes);
-        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
index 710ca48..3c368fb 100644
--- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
@@ -45,7 +45,6 @@
     internal class CallSafeHandle : SafeHandleZeroIsInvalid, INativeCall
     {
         public static readonly CallSafeHandle NullInstance = new CallSafeHandle();
-        static readonly Encoding EncodingUTF8 = System.Text.Encoding.UTF8;
         static readonly NativeMethods Native = NativeMethods.Get();
 
         const uint GRPC_WRITE_BUFFER_HINT = 1;
@@ -140,7 +139,7 @@
                 var ctx = BatchContextSafeHandle.Create();
                 var optionalPayloadLength = optionalPayload != null ? new UIntPtr((ulong)optionalPayload.Length) : UIntPtr.Zero;
                 completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
-                var statusDetailBytes = EncodingUTF8.GetBytes(status.Detail);
+                var statusDetailBytes = MarshalUtils.GetBytesUTF8(status.Detail);
                 Native.grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, statusDetailBytes, new UIntPtr((ulong)statusDetailBytes.Length), metadataArray, sendEmptyInitialMetadata,
                     optionalPayload, optionalPayloadLength, writeFlags).CheckOk();
             }
@@ -204,6 +203,11 @@
             }
         }
 
+        public AuthContextSafeHandle GetAuthContext()
+        {
+            return Native.grpcsharp_call_auth_context(this);
+        }
+
         protected override bool ReleaseHandle()
         {
             Native.grpcsharp_call_destroy(handle);
diff --git a/src/csharp/Grpc.Core/Internal/MarshalUtils.cs b/src/csharp/Grpc.Core/Internal/MarshalUtils.cs
new file mode 100644
index 0000000..897e2f7
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/MarshalUtils.cs
@@ -0,0 +1,90 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Grpc.Core.Internal
+{
+    /// <summary>
+    /// Useful methods for native/managed marshalling.
+    /// </summary>
+    internal static class MarshalUtils
+    {
+        static readonly Encoding EncodingUTF8 = System.Text.Encoding.UTF8;
+        static readonly Encoding EncodingASCII = System.Text.Encoding.ASCII;
+
+        /// <summary>
+        /// Converts <c>IntPtr</c> pointing to a UTF-8 encoded byte array to <c>string</c>.
+        /// </summary>
+        public static string PtrToStringUTF8(IntPtr ptr, int len)
+        {
+            var bytes = new byte[len];
+            Marshal.Copy(ptr, bytes, 0, len);
+            return EncodingUTF8.GetString(bytes);
+        }
+
+        /// <summary>
+        /// Returns byte array containing UTF-8 encoding of given string.
+        /// </summary>
+        public static byte[] GetBytesUTF8(string str)
+        {
+            return EncodingUTF8.GetBytes(str);
+        }
+
+        /// <summary>
+        /// Get string from a UTF8 encoded byte array.
+        /// </summary>
+        public static string GetStringUTF8(byte[] bytes)
+        {
+            return EncodingUTF8.GetString(bytes);
+        }
+
+        /// <summary>
+        /// Returns byte array containing ASCII encoding of given string.
+        /// </summary>
+        public static byte[] GetBytesASCII(string str)
+        {
+            return EncodingASCII.GetBytes(str);
+        }
+
+        /// <summary>
+        /// Get string from an ASCII encoded byte array.
+        /// </summary>
+        public static string GetStringASCII(byte[] bytes)
+        {
+            return EncodingASCII.GetString(bytes);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/Internal/NativeMethods.cs b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
index aff9550..dd65f05 100644
--- a/src/csharp/Grpc.Core/Internal/NativeMethods.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
@@ -148,6 +148,12 @@
         public readonly Delegates.grpcsharp_server_shutdown_and_notify_callback_delegate grpcsharp_server_shutdown_and_notify_callback;
         public readonly Delegates.grpcsharp_server_destroy_delegate grpcsharp_server_destroy;
 
+        public readonly Delegates.grpcsharp_call_auth_context_delegate grpcsharp_call_auth_context;
+        public readonly Delegates.grpcsharp_auth_context_peer_identity_property_name_delegate grpcsharp_auth_context_peer_identity_property_name;
+        public readonly Delegates.grpcsharp_auth_context_property_iterator_delegate grpcsharp_auth_context_property_iterator;
+        public readonly Delegates.grpcsharp_auth_property_iterator_next_delegate grpcsharp_auth_property_iterator_next;
+        public readonly Delegates.grpcsharp_auth_context_release_delegate grpcsharp_auth_context_release;
+
         public readonly Delegates.gprsharp_now_delegate gprsharp_now;
         public readonly Delegates.gprsharp_inf_future_delegate gprsharp_inf_future;
         public readonly Delegates.gprsharp_inf_past_delegate gprsharp_inf_past;
@@ -256,6 +262,12 @@
             this.grpcsharp_server_shutdown_and_notify_callback = GetMethodDelegate<Delegates.grpcsharp_server_shutdown_and_notify_callback_delegate>(library);
             this.grpcsharp_server_destroy = GetMethodDelegate<Delegates.grpcsharp_server_destroy_delegate>(library);
 
+            this.grpcsharp_call_auth_context = GetMethodDelegate<Delegates.grpcsharp_call_auth_context_delegate>(library);
+            this.grpcsharp_auth_context_peer_identity_property_name = GetMethodDelegate<Delegates.grpcsharp_auth_context_peer_identity_property_name_delegate>(library);
+            this.grpcsharp_auth_context_property_iterator = GetMethodDelegate<Delegates.grpcsharp_auth_context_property_iterator_delegate>(library);
+            this.grpcsharp_auth_property_iterator_next = GetMethodDelegate<Delegates.grpcsharp_auth_property_iterator_next_delegate>(library);
+            this.grpcsharp_auth_context_release = GetMethodDelegate<Delegates.grpcsharp_auth_context_release_delegate>(library);
+
             this.gprsharp_now = GetMethodDelegate<Delegates.gprsharp_now_delegate>(library);
             this.gprsharp_inf_future = GetMethodDelegate<Delegates.gprsharp_inf_future_delegate>(library);
             this.gprsharp_inf_past = GetMethodDelegate<Delegates.gprsharp_inf_past_delegate>(library);
@@ -404,6 +416,12 @@
             public delegate void grpcsharp_server_shutdown_and_notify_callback_delegate(ServerSafeHandle server, CompletionQueueSafeHandle cq, BatchContextSafeHandle ctx);
             public delegate void grpcsharp_server_destroy_delegate(IntPtr server);
 
+            public delegate AuthContextSafeHandle grpcsharp_call_auth_context_delegate(CallSafeHandle call);
+            public delegate IntPtr grpcsharp_auth_context_peer_identity_property_name_delegate(AuthContextSafeHandle authContext);  // returns const char*
+            public delegate AuthContextSafeHandle.NativeAuthPropertyIterator grpcsharp_auth_context_property_iterator_delegate(AuthContextSafeHandle authContext);
+            public delegate IntPtr grpcsharp_auth_property_iterator_next_delegate(ref AuthContextSafeHandle.NativeAuthPropertyIterator iterator);  // returns const auth_property*
+            public delegate void grpcsharp_auth_context_release_delegate(IntPtr authContext);
+
             public delegate Timespec gprsharp_now_delegate(ClockType clockType);
             public delegate Timespec gprsharp_inf_future_delegate(ClockType clockType);
             public delegate Timespec gprsharp_inf_past_delegate(ClockType clockType);
diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs
index 6fc715d..6e195b4 100644
--- a/src/csharp/Grpc.Core/Metadata.cs
+++ b/src/csharp/Grpc.Core/Metadata.cs
@@ -32,12 +32,10 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Globalization;
-using System.Runtime.InteropServices;
 using System.Text;
 using System.Text.RegularExpressions;
 
+using Grpc.Core.Internal;
 using Grpc.Core.Utils;
 
 namespace Grpc.Core
@@ -242,7 +240,6 @@
         /// </summary>
         public class Entry
         {
-            private static readonly Encoding Encoding = Encoding.ASCII;
             private static readonly Regex ValidKeyRegex = new Regex("^[a-z0-9_-]+$");
 
             readonly string key;
@@ -306,7 +303,7 @@
                 {
                     if (valueBytes == null)
                     {
-                        return Encoding.GetBytes(value);
+                        return MarshalUtils.GetBytesASCII(value);
                     }
 
                     // defensive copy to guarantee immutability
@@ -324,7 +321,7 @@
                 get
                 {
                     GrpcPreconditions.CheckState(!IsBinary, "Cannot access string value of a binary metadata entry");
-                    return value ?? Encoding.GetString(valueBytes);
+                    return value ?? MarshalUtils.GetStringASCII(valueBytes);
                 }
             }
 
@@ -358,7 +355,7 @@
             /// </summary>
             internal byte[] GetSerializedValueUnsafe()
             {
-                return valueBytes ?? Encoding.GetBytes(value);
+                return valueBytes ?? MarshalUtils.GetBytesASCII(value);
             }
 
             /// <summary>
@@ -371,7 +368,7 @@
                 {
                     return new Entry(key, null, valueBytes);
                 }
-                return new Entry(key, Encoding.GetString(valueBytes), null);
+                return new Entry(key, MarshalUtils.GetStringASCII(valueBytes), null);
             }
 
             private static string NormalizeKey(string key)
diff --git a/src/csharp/Grpc.Core/ServerCallContext.cs b/src/csharp/Grpc.Core/ServerCallContext.cs
index 8f28fbc..c8950b7 100644
--- a/src/csharp/Grpc.Core/ServerCallContext.cs
+++ b/src/csharp/Grpc.Core/ServerCallContext.cs
@@ -32,7 +32,6 @@
 #endregion
 
 using System;
-using System.Runtime.CompilerServices;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -56,6 +55,7 @@
         private Status status = Status.DefaultSuccess;
         private Func<Metadata, Task> writeHeadersFunc;
         private IHasWriteOptions writeOptionsHolder;
+        private Lazy<AuthContext> authContext;
 
         internal ServerCallContext(CallSafeHandle callHandle, string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken,
             Func<Metadata, Task> writeHeadersFunc, IHasWriteOptions writeOptionsHolder)
@@ -68,6 +68,7 @@
             this.cancellationToken = cancellationToken;
             this.writeHeadersFunc = writeHeadersFunc;
             this.writeOptionsHolder = writeOptionsHolder;
+            this.authContext = new Lazy<AuthContext>(GetAuthContextEager);
         }
 
         /// <summary>
@@ -187,6 +188,26 @@
                 writeOptionsHolder.WriteOptions = value;
             }
         }
+
+        /// <summary>
+        /// Gets the <c>AuthContext</c> associated with this call.
+        /// Note: Access to AuthContext is an experimental API that can change without any prior notice.
+        /// </summary>
+        public AuthContext AuthContext
+        {
+            get
+            {
+                return authContext.Value;
+            }
+        }
+
+        private AuthContext GetAuthContextEager()
+        {
+            using (var authContextNative = callHandle.GetAuthContext())
+            {
+                return authContextNative.ToAuthContext();
+            }
+        }
     }
 
     /// <summary>
diff --git a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
index f85e272..377dad6 100644
--- a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
@@ -37,6 +37,7 @@
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Google.Protobuf;
 using Grpc.Core;
 using Grpc.Core.Utils;
 using Grpc.Testing;
@@ -68,7 +69,7 @@
 
             server = new Server
             {
-                Services = { TestService.BindService(new TestServiceImpl()) },
+                Services = { TestService.BindService(new SslCredentialsTestServiceImpl()) },
                 Ports = { { Host, ServerPort.PickUnused, serverCredentials } }
             };
             server.Start();
@@ -95,5 +96,40 @@
             var response = client.UnaryCall(new SimpleRequest { ResponseSize = 10 });
             Assert.AreEqual(10, response.Payload.Body.Length);
         }
+
+        [Test]
+        public async Task AuthContextIsPopulated()
+        {
+            var call = client.StreamingInputCall();
+            await call.RequestStream.CompleteAsync();
+            var response = await call.ResponseAsync;
+            Assert.AreEqual(12345, response.AggregatedPayloadSize);
+        }
+
+        private class SslCredentialsTestServiceImpl : TestService.TestServiceBase
+        {
+            public override async Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
+            {
+                return new SimpleResponse { Payload = CreateZerosPayload(request.ResponseSize) };
+            }
+
+            public override async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context)
+            {
+                var authContext = context.AuthContext;
+                await requestStream.ForEachAsync(async request => {});
+
+                Assert.IsTrue(authContext.IsPeerAuthenticated);
+                Assert.AreEqual("x509_subject_alternative_name", authContext.PeerIdentityPropertyName);
+                Assert.IsTrue(authContext.PeerIdentity.Count() > 0);
+                Assert.AreEqual("ssl", authContext.FindPropertiesByName("transport_security_type").First().Value);
+
+                return new StreamingInputCallResponse { AggregatedPayloadSize = 12345 };
+            }
+
+            private static Payload CreateZerosPayload(int size)
+            {
+                return new Payload { Body = ByteString.CopyFrom(new byte[size]) };
+            }
+        }
     }
 }
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index 6a24119..491df4d 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -1023,6 +1023,31 @@
   return grpc_metadata_credentials_create_from_plugin(plugin, NULL);
 }
 
+/* Auth context */
+
+GPR_EXPORT grpc_auth_context *GPR_CALLTYPE grpcsharp_call_auth_context(grpc_call *call) {
+  return grpc_call_auth_context(call);
+}
+
+GPR_EXPORT const char *GPR_CALLTYPE grpcsharp_auth_context_peer_identity_property_name(
+    const grpc_auth_context *ctx) {
+  return grpc_auth_context_peer_identity_property_name(ctx);
+}
+
+GPR_EXPORT grpc_auth_property_iterator GPR_CALLTYPE
+grpcsharp_auth_context_property_iterator(const grpc_auth_context *ctx) {
+  return grpc_auth_context_property_iterator(ctx);
+}
+
+GPR_EXPORT const grpc_auth_property *GPR_CALLTYPE grpcsharp_auth_property_iterator_next(
+    grpc_auth_property_iterator *it) {
+  return grpc_auth_property_iterator_next(it);
+}
+
+GPR_EXPORT void GPR_CALLTYPE grpcsharp_auth_context_release(grpc_auth_context *ctx) {
+  grpc_auth_context_release(ctx);
+}
+
 /* Logging */
 
 typedef void(GPR_CALLTYPE *grpcsharp_log_func)(const char *file, int32_t line,
diff --git a/src/csharp/tests.json b/src/csharp/tests.json
index 4ce6769..63f2e97 100644
--- a/src/csharp/tests.json
+++ b/src/csharp/tests.json
@@ -8,6 +8,8 @@
     "Grpc.Core.Internal.Tests.MetadataArraySafeHandleTest",
     "Grpc.Core.Internal.Tests.TimespecTest",
     "Grpc.Core.Tests.AppDomainUnloadTest",
+    "Grpc.Core.Tests.AuthContextTest",
+    "Grpc.Core.Tests.AuthPropertyTest",
     "Grpc.Core.Tests.CallCredentialsTest",
     "Grpc.Core.Tests.CallOptionsTest",
     "Grpc.Core.Tests.ChannelCredentialsTest",