32

I started to build a web api for mobile apps and I'm having a hard time with implementing authentication. I use Bearer and although everything is supposed to be fine, I cannot get the current user from action in controller. HttpContext.Current.User.Identity.Name is null (the same is result of HttpContext.Current.User.Identity.GetUserId()). Here are the pieces of important code:

Startup.cs:

    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var config = new HttpConfiguration();
            ConfigureAuth(app);
            WebApiConfig.Register(config);
            app.UseWebApi(config);
        }
    }

Startup.Auth.cs

public partial class Startup
{
        static Startup()
        {
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/token"),
                Provider = new ApplicationOAuthProvider(),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                AllowInsecureHttp = true
            };

            OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
        }

        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
        public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }


        public static string PublicClientId { get; private set; }

        public void ConfigureAuth(IAppBuilder app)
        {
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
            {
                AccessTokenProvider = new AuthenticationTokenProvider()
            });
            app.UseOAuthBearerTokens(OAuthOptions);

            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        }
}

ApplicationOAuthProvider.cs:

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {

            string clientId, clientSecret;

            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
            {
                return SetErrorAndReturn(context, "client error", "");
            }

            if (clientId == "secret" && clientSecret == "secret")
            {
                context.Validated();
                return Task.FromResult<object>(null);
            }

            return SetErrorAndReturn(context, "client error", "");
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {

            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

            using (AuthRepository _repo = new AuthRepository())
            {
                IdentityUser user = await _repo.FindUser(context.UserName, context.Password);

                if (user == null)
                {
                    context.SetError("invalid_grant", "The user name or password is incorrect.");
                    return;
                }
            }

            var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            identity.AddClaim(new Claim("sub", context.UserName));
            identity.AddClaim(new Claim("role", "user"));

            context.Validated(identity);
        }


        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }

            return Task.FromResult<object>(null);
        }

AuthRepository.cs:

public class AuthRepository : IDisposable
    {
        private readonly AuthContext _ctx;

        private readonly UserManager<IdentityUser> _userManager;

        public AuthRepository()
        {
            _ctx = new AuthContext();
            _userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(_ctx));
        }

        public async Task<IdentityResult> RegisterUser(UserModel userModel)
        {
            var user = new IdentityUser
            {
                UserName = userModel.UserName
            };

            var result = await _userManager.CreateAsync(user, userModel.Password);

            return result;
        }

        public async Task<IdentityUser> FindUser(string userName, string password)
        {
            IdentityUser user = await _userManager.FindAsync(userName, password);
            return user;
        }

        public void Dispose()
        {
            _ctx.Dispose();
            _userManager.Dispose();

        }
    }

AuthContext.cs:

public class AuthContext : IdentityDbContext<IdentityUser>
    {
        public AuthContext()
            : base("AuthContext")
        {

        }
    }

And finnaly ValuesController.cs:

[Authorize]
public class ValuesController : ApiController
{

    public IEnumerable<string> Get()
    {
        return new String[] {HttpContext.Current.User.Identity.Name, HttpContext.Current.User.Identity.GetUserId(),ClaimsPrincipal.Current.Identity.Name};
    }
}

When i go to this action, i get null 3 times. Despite that, the whole authentication proccess seems to be fine - only when i send a good token, i have access. Does anybody have an idea what is wrong here?

tereško
  • 58,060
  • 25
  • 98
  • 150
user3558203
  • 359
  • 1
  • 4
  • 12
  • Do you have a breakpoint set on `return new String[] { HttpContext...`? Where is the first null? – user1477388 Sep 25 '14 at 19:23
  • The last not null value is Identity (so i dont get an error,but null as a returned value) – user3558203 Sep 25 '14 at 19:26
  • Is `GetUserId()` also null? – user1477388 Sep 25 '14 at 19:32
  • Yes, this method also gives me null. – user3558203 Sep 25 '14 at 19:34
  • I don't see your login method where you should be called `FormsAuthentication.SetAuthCookie(username, true);`. Ref. http://stackoverflow.com/questions/1056487/httpcontext-current-user-identity-name-is-always-string-empty – user1477388 Sep 25 '14 at 20:15
  • Should I really do this? I mean, i don't want to use cookies (api will be for mobile apps). And based on this article (from which I learned): http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/ - I belive i dont need to use it. Or maybe i didn't understand something. – user3558203 Sep 25 '14 at 20:32

1 Answers1

68

On method GrantResourceOwnerCredentials once you add claims after validating the username password you need to add this claim:

identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));

By doing this the UserId will be filled when you call User.Identity.Name inside protected controller. Hope this solves your issue.

Taiseer Joudeh
  • 8,953
  • 1
  • 41
  • 45
  • 1
    Unfortunately, it didn't helped. I still get null. I am not sure I understood the part with protected controller btw. What i did was simply adding the line of yours after identity.AddClaim(new Claim("role", "user")); – user3558203 Sep 25 '14 at 20:53
  • 2
    I mean by Protected controller a controller attribute with [Authorize], if you checked the value User.Identity.IsAuthenticated is it set to true and User.Identity.Name is always empty after adding this claim? – Taiseer Joudeh Sep 25 '14 at 21:24
  • 3
    Also want to thank you for the above solution! Just to clarify what user55 said.. you have to add the claim in the provider and then re-log into to create a new token before you'll see the change (I was refreshing the page with my existing token and wondering why I couldn't see the username, ha) – alimac83 Dec 27 '14 at 22:32