ASP.NET Web API and Owin OAuth: Calling User-Related Web APIs

Keywords: ASP.NET Mobile encoding ascii

Reference page:

http://www.yuanjiaocheng.net/webapi/web-api-route.html

http://www.yuanjiaocheng.net/webapi/parameter-binding.html

http://www.yuanjiaocheng.net/webapi/action-method-returntype.html

http://www.yuanjiaocheng.net/webapi/web-api-reqresq-format.html

http://www.yuanjiaocheng.net/webapi/media-formatter.html

In the previous article Bowen In OAuth, we successfully invoked the user-independent Web API by using Acess Token authorized by Client Credential Grant (only validating the calling client, not the logged-in user).

In this blog post, we will use OAuth's uuuuuuuuuuuu Resource Owner Password Credentials Grant Access Token is acquired by grant_type=password, and the user-related Web API is invoked by this Token.

The corresponding application scenario is to develop mobile App (non-third-party App) for its own website, which only requires users to log on to App without authorizing the data that App can access.

According to OAuth Standard Client requests for Access Token are as follows:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

According to the above request mode, a simple client is implemented in C # with HttpClient. The code is as follows:

public class OAuthClientTest
{
    private HttpClient _httpClient;

    public OAuthClientTest()
    {
        _httpClient = new HttpClient();
        _httpClient.BaseAddress = new Uri("http://openapi.cnblogs.com");
    } 

    [Fact]
    public async Task Get_Accesss_Token_By_Resource_Owner_Password_Credentials_Grant()
    {
        Console.WriteLine(await GetAccessToken());
    }

    private async Task<string> GetAccessToken()
    {
        var clientId = "1234";
        var clientSecret = "5678";

        var parameters = new Dictionary<string, string>();            
        parameters.Add("grant_type", "password");
        parameters.Add("username", "Blog Garden Team");
        parameters.Add("password", "cnblogs.com");

        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
            "Basic",
            Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret))
            );

        var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
        var responseValue = await response.Content.ReadAsStringAsync();
        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            return JObject.Parse(responseValue)["access_token"].Value<string>();
        }
        else
        {
            Console.WriteLine(responseValue);
            return string.Empty;
        }
    }
}

(Note: The client_id/client_secret here is changed to Basic Authorization to better comply with the OAuth specification than before)

On the server side, based on Owin OAuth, for the authorization mode of Resource Owner Password Credentials Grant, it only needs to overload the OAuth Authorization Server Provider. Grant Resource Owner Credentials () method. The code is as follows:

public class CNBlogsAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    //...

    public override async Task GrantResourceOwnerCredentials(
        OAuthGrantResourceOwnerCredentialsContext context)
    {
        //Call the background login service to verify the username and password

        var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
        oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
        context.Validated(ticket);

        await base.GrantResourceOwnerCredentials(context);
    }
}

The complete CNBlogs Authorization Server Provider implementation code is as follows (context.TryGetFormCredentials instead of context. TryGetBasic Credentials):

public class CNBlogsAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        string clientId;
        string clientSecret;
        context.TryGetBasicCredentials(out clientId, out clientSecret);

        if (clientId == "1234"
            && clientSecret == "5678")
        {
            context.Validated(clientId);
        }

        await base.ValidateClientAuthentication(context);
    }

    public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
    {
        var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
        var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
        context.Validated(ticket);

        await base.GrantClientCredentials(context);
    }

    public override async Task GrantResourceOwnerCredentials(
        OAuthGrantResourceOwnerCredentialsContext context)
    {
        //Call the background login service to verify the username and password

        var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
        oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
        context.Validated(ticket);

        await base.GrantResourceOwnerCredentials(context);
    }
}
CNBlogsAuthorizationServerProvider

In this way, you can get Access Token by running the client program.

Next, we can call the user-related Web API with the AccessToken obtained in this way.

On the server side, we test it with a simple Web API. The code is as follows:

public class UsersController : ApiController
{
    [Authorize]
    public string GetCurrent()
    {
        return User.Identity.Name;
        //Here you can call back-end user service, get the number of relevant users, or authenticate user privileges for corresponding operations.
    }
}

Then, the client invokes the Web API with Acess Token obtained by grant_type=password. The code added by the client is as follows:

[Fact]
public async Task Call_WebAPI_By_Resource_Owner_Password_Credentials_Grant()
{
    var token = await GetAccessToken();
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    Console.WriteLine(await (await _httpClient.GetAsync("/api/users/current")).Content.ReadAsStringAsync());
}

The results of client operation are as follows:

"Blog Garden Team"

The call was successful! The result is the username used to get Access Token.  

Combined with the existing security mechanism of ASP.NET and the power of OWIN, Microsoft.Owin.Security.OAuth does make it easier to develop Web API s based on OAuth.

Posted by twilitegxa on Wed, 12 Dec 2018 13:48:06 -0800