4
\$\begingroup\$

I have been implementing the OAuth 2.0 implicit flow to secure the API I use in an Angular SAP. Please, could you review my code? I am sure there is a lot of stuff to improve in it.

I have separated the API into two instances:

  1. Authentication server which checks the user and password and issues tokens.
  2. Resource server which holds the actual API which is protected with the use of tokens generated in the Authentication server.

Is it a viable option to consider substituting my Authentication server with some open source solution like Thinktecture IdentityServer?

The use of OAuth 2.0 in the resource server boils down to including the following in the Startup class:

    private void ConfigureOAuthTokenConsumption(IAppBuilder app)
    {
        OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
        //Token Consumption
        app.UseOAuthBearerAuthentication(OAuthBearerOptions);
    }

Here is the code I use in Authentication server:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();

        ConfigureOAuth(app);

        WebApiConfig.Register(config);

        var container = ConfigureAutoFac(config);
        app.UseAutofacMiddleware(container);

        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        app.UseWebApi(config);            
    }

    public void ConfigureOAuth(IAppBuilder app)
    {
        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(10),
            Provider = new SimpleAuthorizationServerProvider()
        };

        // Token Generation
        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    }        

    private IContainer ConfigureAutoFac(HttpConfiguration config)
    {
        var builder = new ContainerBuilder();

        builder.RegisterType<AccountController>().InstancePerRequest();

        // Set the dependency resolver to be Autofac.
        var container = builder.Build();
        config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

        return container;
    }
}

Here is the authorization server provider:

public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        // We are dealing with a browser based javascipt app so no clientId and client secret are transmitted.
        // There is nothing to validate
        context.Validated();
        return Task.FromResult<object>(null);
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {

        var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");

        if (allowedOrigin == null) allowedOrigin = "*";

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

        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(ClaimTypes.Name, context.UserName));
        identity.AddClaim(new Claim("sub", context.UserName));
        identity.AddClaim(new Claim("role", "user"));

        var props = new AuthenticationProperties(new Dictionary<string, string>
            {
                {
                    "userName", context.UserName
                }
            });

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

    }

    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);
    }
}

Here is the AuthContext class allowing access to the database. I'm not really sure if it is necessary:

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

The actual AuthRepository which requires the AuthContext:

public class AuthRepository : IDisposable
{
    private AuthContext _ctx;

    private UserManager<IdentityUser> _userManager;

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

    public async Task<IdentityResult> RegisterUser(UserModel userModel)
    {
        IdentityUser 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 async Task<IdentityUser> FindAsync(UserLoginInfo loginInfo)
    {
        IdentityUser user = await _userManager.FindAsync(loginInfo);

        return user;
    }

    public async Task<IdentityResult> CreateAsync(IdentityUser user)
    {
        var result = await _userManager.CreateAsync(user);

        return result;
    }

    public async Task<IdentityResult> AddLoginAsync(string userId, UserLoginInfo login)
    {
        var result = await _userManager.AddLoginAsync(userId, login);

        return result;
    }

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

    }
}

Is that approach correct? If not, what should i change?

\$\endgroup\$
  • \$\begingroup\$ Angular SAP, you're referring to SPA, correct? \$\endgroup\$ – Halter Aug 21 '17 at 21:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.