Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supports authentication #42

Closed
yunusefendi52 opened this issue Feb 12, 2018 · 6 comments
Closed

Supports authentication #42

yunusefendi52 opened this issue Feb 12, 2018 · 6 comments

Comments

@yunusefendi52
Copy link

Would be great if supports authentication, especially when dealing with Web API

@Mimetis
Copy link
Owner

Mimetis commented Feb 13, 2018

Hey @5yunus2efendi

I have worked on your authentication issue. Actually it was in my backlog... for a long time.
Thx for submitting it, it, eventually it has motivated me to implement it and test it !

I have made a bunch of tests, and my solution is based on a Bearer token which I pass to the Dotmim.Sync WebProxyClientProvider object.

I will go deeper in the explanation, but to let you know, here is the most important code, when you implement your proxy on the client side:

var proxyClientProvider = new WebProxyClientProvider(new Uri("http://localhost:58507/api/authsync"));

// adding bearer auth
if (authenticationResult != null && authenticationResult.AccessToken != null)
    proxyClientProvider.AddCustomHeader("Authorization", authenticationResult.CreateAuthorizationHeader());

var agent = new SyncAgent(clientProvider, proxyClientProvider);
var r = await agent.SynchronizeAsync();

Ok, now here is the full story

Introduction

To be able to authenticate your sync queries, the first assumption is to work with a client -server project, based on the Dotmim.Sync.Web.WebProxyClientProvider and Dotmim.Sync.Web.WebProxyServerProvider

For more information about how to implement a webserver project, see the documentation here : Implementing a web server proxy

Since your server is a web server, you have to be sure no one is able to reach your sync endpoint, with no autorization...

Server Side

On the server side, it's not mandatory to implement a full OAUTH2 (or whatever you want) server.
You can rely on, for example, the Azure Active Directory authentication process.

For this sample, I will work with the Azure AD V2 endpoints, that you can configure here: http://apps.dev.microsoft.com/

My application is configured as a Web Api endpoint, with a custom scope, called access_as_user

image
Enventually, the most important things to remember are:

  • The Client Id
  • The Scope you just have created

Once you're ready, you can modify your ASP.NET Core 2.0 web server, to become a JwtBearer authenticated web sever.

First of all, in the Startup.cs file, add this code:

var connectionString = Configuration.GetSection("ConnectionStrings")["DefaultConnection"];
services.AddSyncServer<SqlSyncProvider>(connectionString, configuration =>
{
    var s = new string[] { "Employees" };
    configuration.Add(s);
    configuration.DownloadBatchSizeInKB = 1000;
});

// Use bearer schema for authentication
services.AddAuthentication(sharedOptions =>
{
    sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

})
    .AddJwtBearer(options =>
    {
        var azureAdOptions = Configuration.GetSection("AzureAdV2").Get<AzureAdOptions>();

        options.Audience = azureAdOptions.ClientId;
        options.Authority = azureAdOptions.Authority;

        options.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateAudience = false,
            ValidateIssuer = false,
            ValidateIssuerSigningKey = false
        };
    });

services.AddMvc();

As you can see, the AddJwtBearer() method use a section from my config file to retrieve my ClientId and my Authority. Here is my appsettings.json file:

{
  "AzureAdV2": {
    "Authority": "https://login.microsoftonline.com/common/v2.0",
    "ClientId": "99362e01-d41a-4370-95ce-db9e1d51796f"
  }
}

Ok, now we just have to mark any controller with the [Authenticate] attribute to be sure we won't be allowed until we pass a bearer token with any requests we will try !

[Authorize]
[Route("api/[controller]")]
public class AuthSyncController : Controller
{
    // proxy to handle requests and send them to SqlSyncProvider
    private WebProxyServerProvider webProxyServer;

    // Injected thanks to Dependency Injection
    public AuthSyncController(WebProxyServerProvider proxy)
    {
        webProxyServer = proxy;
    }

    // POST api/values
    [HttpPost]
    public async Task Post()
    {
        // Checking the scope is optional
        // The [Authorize] class attribute is enough, since it prevents anyone to access
        // this controller without a Bearer token
        // Anyway you can have a more detailed control using the claims !
        string scope = (User.FindFirst("http://schemas.microsoft.com/identity/claims/scope"))?.Value;
        string user = (User.FindFirst(ClaimTypes.NameIdentifier))?.Value;

        if (scope != "access_as_user")
        {
            this.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }

        await webProxyServer.HandleRequestAsync(this.HttpContext);
    }

}

As you can see, you can fine tuning your autorization process with the claims.

Client Side

On the client side, you will have to authenticate your user, to be able to retrieve a bearer token, and then use it to authenticate your sync process.
For instance, I use the MSAL framework to be able to authenticate my user.

// Authentication sample config
// -------------------------------------------------------------------
// App is registered in https://apps.dev.microsoft.com
// -------------------------------------------------------------------
string ClientId = "99362e01-d41a-4370-95ce-db9e1d51796f"; // AAD V2
PublicClientApplication publicClientApp = new PublicClientApplication(ClientId);
string[] scopes = new string[] { "api://99362e01-d41a-4370-95ce-db9e1d51796f/access_as_user" };
AuthenticationResult authenticationResult = null;

try
{
    var user = publicClientApp.Users.FirstOrDefault();
    authenticationResult = await publicClientApp.AcquireTokenSilentAsync(scopes, user);
}
catch (MsalUiRequiredException)
{
    try
    {
        authenticationResult = await publicClientApp.AcquireTokenAsync(scopes);
    }
    catch (MsalException msalex)
    {
        Debug($"Error Acquiring Token");
    }
}
catch (Exception ex)
{
    Debug(ex.Message);
}

Then I use the bearer token to authenticate my sync request:

var clientProvider = new SqliteSyncProvider("employees.db");

var proxyClientProvider = new WebProxyClientProvider(
    new Uri("http://localhost:58507/api/authsync"));

// adding bearer auth
if (authenticationResult != null && authenticationResult.AccessToken != null)
    proxyClientProvider.AddCustomHeader("Authorization", authenticationResult.CreateAuthorizationHeader());

var agent = new SyncAgent(clientProvider, proxyClientProvider);

agent.SyncProgress += (s, a) => Debug(a.Message + a.PropertiesMessage);
try
{
    var r = await agent.SynchronizeAsync();
    Debug("TotalChangesDownloaded: " + r.TotalChangesDownloaded);
}
catch (Exception ex)
{

    Debug("Error during sync " + ex.Message);
}

Conclusion and sample

The authentication is fully integrated with any kind of authentication you want to use.
For instance, I used a OAUTH2 process, but you can use anything you want, cookie, identity provider, sql database and so on.
The most important is to be sure that a middleware is existing for ASP.NET Core.

In my next commit, you will find a full sample in the /Samples folder (I hope before the end of the week)

Let me know if it's ok for you ?

Sebastien

@yunusefendi52
Copy link
Author

yunusefendi52 commented Feb 13, 2018

sounds great, I already implemented bearer token on server-side, now I am waiting for client-side to get done, looking forward for this! 😄

@Mimetis
Copy link
Owner

Mimetis commented Feb 13, 2018

Just for information, what kind of technic did you use to implement the server side ?

My sample is based on .Net Core 2.0 with MVC / JwtBearer Middleware, but I'm curious about others technics.

BTW, the version 0.1.9 is actually implementing the needed properties and methods to works properly with authentication.

Check the new version here : https://www.nuget.org/packages/Dotmim.Sync.Web/0.1.9

For instance, once nuget cache website will be updated, 0.1.9 will be the default version.

@Mimetis Mimetis closed this as completed Feb 13, 2018
@yunusefendi52
Copy link
Author

for server-side I am using .Net Core 2.0 with Web Api / JwtBearer, but I am not using Azure Active Directory though, I implemented my own database to store users, maybe I'll try Azure Active Directory later! 😄

It's looking good, I use the authentication and it's working!

@Mimetis
Copy link
Owner

Mimetis commented Feb 14, 2018

Oh nice;
Eventually your solution is probably a better "starter" to understand the authentication process.
Can you share a sample of your implementation ? (if possible of course)

@yunusefendi52
Copy link
Author

yunusefendi52 commented Feb 14, 2018

Here is the sample:
LearningDotmimSync.zip

AFAIK, SecretKey must be known to both of the sender and the receiver right? So what do you think is the best way to store SecretKey (see at AppSettings.json)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants