Madurika Welivita
Madurika Welivita

Reputation: 900

How to implement REST API with OAuth 2.0 for multiple client access

I have a requirement like below to implement REST API using OAuth 2.0 and Web Api.

REST API should allow - to create, update, view and delete orders - to create, update, view and delete inventories

API should be able to used by any type of external client such as web application, mobile application, windows/web services, etc.

Roles allowed for external clients : Order Management , Inventory Management User data (roles, permissions) of external clients will not be managed by our system.

Note: There can be another two roles like Internal , External. Because delete functions can't be allowed for external users.

Order and Inventory data will be managed in a SQL Server DB which is already used by current windows/desktop applications. Orders, inventories comes via new API should save in same database.

Questions:

  1. Which grant type I can use?
  2. How should I mange external client's data (allowed roles, client id, tokens) ? Do I need to use separate membership database for this? Can I used my existing database with new tables for this?

Upvotes: 2

Views: 8937

Answers (2)

Chamika Sandamal
Chamika Sandamal

Reputation: 24302

You can use Microsoft.Owin.Security.OAuth provider. Please have a look on following sample.

Create new Owin Startup file and change the Configuration method as following

public void Configuration(IAppBuilder app)
{
    var oauthProvider = new OAuthAuthorizationServerProvider
    {
        OnGrantClientCredentials = async context =>
        {

            var claimsIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            // based on clientId get roles and add claims
            claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "Developer"));
            claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "Developer2"));
            context.Validated(claimsIdentity);
        },
        OnValidateClientAuthentication = async context =>
        {
            string clientId;
            string clientSecret;
            // use context.TryGetBasicCredentials in case of passing values in header
            if (context.TryGetFormCredentials(out clientId, out clientSecret))
            {
                if (clientId == "clientId" && clientSecret == "secretKey")
                {
                    context.Validated(clientId);
                }
            }
        }
    };
    var oauthOptions = new OAuthAuthorizationServerOptions
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/accesstoken"),
        Provider = oauthProvider,
        AuthorizationCodeExpireTimeSpan = TimeSpan.FromMinutes(1),
        AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(3),
        SystemClock = new SystemClock()
    };
    app.UseOAuthAuthorizationServer(oauthOptions);
    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

    var config = new HttpConfiguration();
    config.MapHttpAttributeRoutes();
    app.UseWebApi(config);
}

And authorize your API like this

[Authorize(Roles = "Developer")]
// GET: api/Tests
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

you can consume it like following,

string baseAddress = "http://localhost/";
var client = new HttpClient();

// you can pass the values in Authorization header or as form data
//var authorizationHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes("clientId:secretKey"));
//client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authorizationHeader);

var form = new Dictionary<string, string>
    {
        {"grant_type", "client_credentials"},
        {"client_id", "clientId"},
        {"client_secret", "secretKey"},
    };

var tokenResponse = client.PostAsync(baseAddress + "accesstoken", new FormUrlEncodedContent(form)).Result;
var token = tokenResponse.Content.ReadAsAsync<Token>(new[] { new JsonMediaTypeFormatter() }).Result;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
var authorizedResponse = client.GetAsync(baseAddress + "/api/Tests").Result;

Token.cs

internal class Token
{
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }

    [JsonProperty("token_type")]
    public string TokenType { get; set; }

    [JsonProperty("expires_in")]
    public int ExpiresIn { get; set; }

    [JsonProperty("refresh_token")]
    public string RefreshToken { get; set; }
}

answers to your questions

  1. You can use client_credentials
  2. Maintain roles in your own database for each client and inside OnGrantClientCredentials just get roles by client id and assign as claims.

Upvotes: 5

Alexander Zeitler
Alexander Zeitler

Reputation: 13109

  1. Here is a starting point which Grant to choose for which Client. In addition, if you build a SPA (even it is a first party client according to the wording from the link), I would prefer Implicit Grant. If you have a question about a particular Grant for a particular Client, create a new question on stackoverflow.

  2. You can use IdentityServer3 with IdentityServer3.EntityFramework and IdentityServer3.AspNetIdentity. You can place IdentityServer tables in an existing database but I would not recommend it for production.

Upvotes: 2

Related Questions