How to create facebook login in ASP.NET Core Web API

in #utopian-io7 years ago (edited)

What Will I Learn?

  • How to use Facebook API
  • How to create facebook login to your application
  • How to use JWT tokens

Requirements

  • C#
  • ASP.NET Core
  • Visual Studio 2015+/ Visual Studio Code

Difficulty

  • Intermediate

Tutorial Contents

In this tutorial I will present how to create logic for Facebook login, using ASP.NET Core Web API. I will show you how to make full service and REST communication with another part application witch can be mobile application or web application.

At the beginning we have to create new project. I will create new ASP.NET Core 2.0 Web API project. I will show you how do it using Visual Studio 2017.

project_1.JPG

project_2.JPG

How will it work?

When user click in the mobile application that he want to login by facebook, mobile application will send login request to facebook API, when credentials are correctly he will receive accessToken from facebook, this token is sending to our API where our API will send to facebook API request for identify user by accessToken. If everything will be ok facebook will return all information about particular user. Our service will create ours tokens and return this to mobile application. Since then mobile appplication will be using our access token for all request.

In the new project I will create new folder with 4 classes. It will be Resource classes, in this classes we will define the models, witch we will be receiving or sending by REST.

image.png

I created 4 classes:

  • FacebookLoginresource
  • FacebookUserResource
  • TokenResource
  • AuthorizationTokenResource

First and second class will be used for facebook login, next 2 classes will be used for authorization particular user in our application using JWT Tokens.

FacebookLoginResource:

    public class FacebookLoginResource
    {
        [Required]
        [StringLength(255)]
        public string facebookToken { get; set; }
    }

FacebookUserResource

    public class FacebookUserResource
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string Picture { get; set; }
    }

TokenResource

    public class TokenResource
    {
        public string Token { get; set; }
        public long Expiry { get; set; }
    }

AuthorizationTokenResource

    public class AuthorizationTokensResource
    {
        public TokenResource AccessToken { get; set; }
        public TokenResource RefreshToken { get; set; }
    }

Next step is creating a couple of services responsible for logic. I will create 3 service, one for communication with our mobile application, second for communication with facebook API, and third for creating JWT tokens.

image.png

AccountService

 public class AccountService
    {
        private readonly FacebookService _facebookService;
        private readonly JwtHandler _jwtHandler;

        public AccountService(FacebookService facebookService, JwtHandler jwtHandler)
        {
            _facebookService = facebookService;
            _jwtHandler = jwtHandler;
        }

        public async Task<AuthorizationTokensResource> FacebookLoginAsync(FacebookLoginResource facebookLoginResource)
        {
            if (string.IsNullOrEmpty(facebookLoginResource.facebookToken))
            {
                throw new Exception("Token is null or empty");
            }

            var facebookUser = await _facebookService.GetUserFromFacebookAsync(facebookLoginResource.facebookToken);
            var domainUser = await unitOfWork.Users.GetAsync(facebookUser.Email);

            return await CreateAccessTokens(domainUser, facebookLoginResource.DeviceId,
                facebookLoginResource.DeviceName);
        }

        private async Task<AuthorizationTokensResource> CreateAccessTokens(User user, string deviceId,
            string deviceName)
        {
            var accessToken = _jwtHandler.CreateAccessToken(user.Id, user.Email, user.Role);
            var refreshToken = _jwtHandler.CreateRefreshToken(user.Id);

            return new AuthorizationTokensResource { AccessToken = accessToken, RefreshToken = refreshToken };
        }
    }

FacebookService

    public class FacebookService
    {
        private readonly HttpClient _httpClient;

        public FacebookService()
        {
            _httpClient = new HttpClient
            {
                BaseAddress = new Uri("https://graph.facebook.com/v2.8/")
            };
            _httpClient.DefaultRequestHeaders
                .Accept
                .Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }


        public async Task<FacebookUserResource> GetUserFromFacebookAsync(string facebookToken)
        {
            var result = await GetAsync<dynamic>(facebookToken, "me", "fields=first_name,last_name,email,picture.width(100).height(100)");
            if (result == null)
            {
                throw new Exception("User from this token not exist");
            }

            var account = new FacebookUserResource()
            {
                Email = result.email,
                FirstName = result.first_name,
                LastName = result.last_name,
                Picture = result.picture.data.url
            };

            return account;
        }

        private async Task<T> GetAsync<T>(string accessToken, string endpoint, string args = null)
        {
            var response = await _httpClient.GetAsync($"{endpoint}?access_token={accessToken}&{args}");
            if (!response.IsSuccessStatusCode)
                return default(T);

            var result = await response.Content.ReadAsStringAsync();

            return JsonConvert.DeserializeObject<T>(result);
        }
    }

JwtHandler

public class JwtHandler
    {
        private readonly IConfiguration _configuration;

        public JwtHandler(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public TokenResource CreateAccessToken(Guid userId, string email, Role role)
        {
            var now = DateTime.UtcNow;
            var claims = new Claim[]
            {
            new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
            new Claim(JwtRegisteredClaimNames.UniqueName, userId.ToString()),
            new Claim(ClaimTypes.Email, email),
            new Claim(ClaimTypes.Role, role.ToString()),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, now.ToTimeStamp().ToString(), ClaimValueTypes.Integer64),
            };

            var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"])),
                SecurityAlgorithms.HmacSha256);
            var expiry = now.AddMinutes(double.Parse(_configuration["Tokens:AccessExpireMinutes"]));
            var jwt = CreateSecurityToken(claims, now, expiry, signingCredentials);
            var token = new JwtSecurityTokenHandler().WriteToken(jwt);

            return CreateTokenResource(token, expiry.ToTimeStamp());
        }

        public TokenResource CreateRefreshToken(Guid userId)
        {
            var now = DateTime.UtcNow;
            var claims = new Claim[]
            {
            new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
            new Claim(JwtRegisteredClaimNames.UniqueName, userId.ToString()),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, now.ToTimeStamp().ToString(), ClaimValueTypes.Integer64),
            };

            var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"])),
                SecurityAlgorithms.HmacSha256);
            var expiry = now.AddMinutes(double.Parse(_configuration["Tokens:RefreshExpireMinutes"]));
            var jwt = CreateSecurityToken(claims, now, expiry, signingCredentials);
            var token = new JwtSecurityTokenHandler().WriteToken(jwt);

            return CreateTokenResource(token, expiry.ToTimeStamp());
        }

        private JwtSecurityToken CreateSecurityToken(IEnumerable<Claim> claims, DateTime now, DateTime expiry, SigningCredentials credentials)
            => new JwtSecurityToken(claims: claims, notBefore: now, expires: expiry, signingCredentials: credentials);

        private static TokenResource CreateTokenResource(string token, long expiry)
            => new TokenResource { Token = token, Expiry = expiry };
    }

AccountService has 2 methods:

  • FacebookLogin
  • CreateAccessTokens

Facebook Login method is taking facebookLoginresource as parameter, then check is facebokToken is null, next step is checking if user with this accesstoken exist in facebook. If everything is ok createaccess token method is calling.

FacebookService has also 2 methods:

  • GetUserFromFacebook
  • GetAsync

first method is taking accessToken as parameter and is using this token to check if is in facebook exist user connected with this token. If facebook return basic information about this user is creating a facebookUser resource witch is returning to access token.

JWTService has method responsible for creating JwtTokens, more information about it you can find in my other tutorial:
JwtTokens

Last part is creating a new Controller

image.png

        private readonly AccountService _accountService;

        public AccountController(AccountService accountService)
        {
            _accountService = accountService;
        }

        [HttpPost]
        [Route("account/login/facebook")]
        public async Task<IActionResult> FacebookLoginAsync([FromBody] FacebookLoginResource resource)
        {
            var authorizationTokens = await _accountService.FacebookLoginAsync(resource);
            return Ok(authorizationTokens);
        }

This controller has only one method for taking facebookLoginResource and returning a authorization tokens.

Curriculum



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Hey @babelek I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]

Congratulations @babelek! You have received a personal award!

1 Year on Steemit
Click on the badge to view your Board of Honor.

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @babelek! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!