2
\$\begingroup\$

Implementing OAuthAuthentication with OWIN and Identity to secure Web API and let external application like Chrome Extension to access our self hosted API for getting and posting data through our server.

I know this is the basic implementation of OAuth. Could this still be improved in terms of security and best practice?

Also, OAuth Authentication is implemented along our existing Cookie Authentication.

WebApiConfig:

public class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
        config.EnableCors(new EnableCorsAttribute("*", "*", "get,post"));

        config.Routes.MapHttpRoute(
            "API_Default",
            "api/{controller}/{id}",
            new {id = RouteParameter.Optional});

    } 
}

Start.Auth

public void ConfigureAuth(IAppBuilder app)
{
    app.CreatePerOwinContext<UserManager>(UserManager.Create);
    app.CreatePerOwinContext<RoleManager>(RoleManager.Create);

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/account/login"),
        Provider = new CookieAuthenticationProvider
        {
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager, User, int>(
                validateInterval: TimeSpan.FromMinutes(Settings.Instance.SessionValidateInterval),
                regenerateIdentityCallback: async (m, u) =>
                {
                    var identity = await m.CreateIdentityAsync(u, DefaultAuthenticationTypes.ApplicationCookie);
                    //omitted some codes
                    return identity;
                },
                getUserIdCallback: u => Convert.ToInt32(u.GetUserId()))
        },
        ExpireTimeSpan = TimeSpan.FromMinutes(Settings.Instance.SessionExpiryTimeout)
    });

    DataProtectionProvider = app.GetDataProtectionProvider();

    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

    //OAuth
    app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
    {
        AllowInsecureHttp = true, //enable this only when debugging.
        ApplicationCanDisplayErrors = true,
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
        TokenEndpointPath = new PathString("/oauth2/token"),
        Provider = new OAuthAuthorizationServerProvider
        {
            OnValidateClientAuthentication = async (context) =>
            {
                context.Validated();

                await Task.FromResult<object>(null);
            },
            OnGrantResourceOwnerCredentials = async (context) =>
            {
                context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

                var manager = context.OwinContext.GetUserManager<UserManager>();
                var user = await manager.FindAsync(context.UserName, context.Password);

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

                if (!user.EmailConfirmed)
                {
                    context.SetError("invalid_grant", "Invalid login");
                    return;
                }

                var identity = await manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalBearer);
                //omitted some codes

                var authProps = new AuthenticationProperties(new Dictionary<string, string>
                {
                    { "client_id", 'Foo' },
                    //omitted some codes
                });

                var ticket = new AuthenticationTicket(identity, null);
                context.Validated(ticket);

                await Task.FromResult<object>(null);
            },
            OnGrantRefreshToken = async (context) => 
            {
                var originalClient = context.Ticket.Properties.Dictionary["client_id"];
                var currentClient = context.ClientId;

                if (originalClient != currentClient)
                {
                    context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
                    return;
                }

                var refreshIdentity = new ClaimsIdentity(context.Ticket.Identity);
                    //omitted some codes

                var refreshTicket = new AuthenticationTicket(refreshIdentity, context.Ticket.Properties);
                context.Validated(refreshTicket);

                await Task.FromResult<object>(null);
            }
        },
        RefreshTokenProvider = new OAuthRefreshTokenProvider()
    });


    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}

Provider

public class OAuthRefreshTokenProvider : IAuthenticationTokenProvider
{
    public async Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        var clientId = context.Ticket.Properties.Dictionary["client_id"];

        if (string.IsNullOrEmpty(clientId))
        {
            return;
        }

        context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(120);
        context.SetToken(context.SerializeTicket());

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

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

        context.DeserializeTicket(context.Token);

        if (context.Ticket == null)
        {
            context.Response.StatusCode = 400; //Bad request
            context.Response.ContentType = "application/json";
            context.Response.ReasonPhrase = "Invalid Token";
            return;
        }

        if (context.Ticket.Properties.ExpiresUtc <= DateTime.UtcNow)
        {
            context.Response.StatusCode = 401; //Unauthorized
            context.Response.ContentType = "application/json";
            context.Response.ReasonPhrase = "Unauthorized";
            return;
        }

        context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(120);
        context.SetTicket(context.Ticket);

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

    public void Create(AuthenticationTokenCreateContext context)
    {
        throw new NotImplementedException();
    }

    public void Receive(AuthenticationTokenReceiveContext context)
    {
        throw new NotImplementedException();
    }
}
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.