Hello friends, here is my second post about social auth with Xamarin Forms and ASP.net core. The previous method, though functional is kind of old and less recommended than this new approach. A few months ago, a new feature was added to Xamarin Essentials, permitting us to easily implement authentication with a back-end API (not only ASP.net core). This feature is better than previous authentication mechanisms, primarily because it avoids us from adding our application credentials inside our mobile app’s source code. And also, it is very easy to implement. Together, we will not only go through the process of implementing JWT social auth with ASP.net core and Xamarin Essentials, but we will also highlight and solve some difficulties you might face when implementing this feature in a real-world application. So, lets dive in.
Xamarin Essentials is constantly improving, and one of the features which were added a few months ago is the Web Authenticator API. This feature provides an abstraction layer over the process of integrating authentication, calling the web browser, Managing redirects e.t.c in our Xamarin App.
HERE is the source code for this post.
What we will do
- Setup Social authentication with the Back-end and mobile app
- Getting user information from social media
- Adding Aspnetcore identity and Generating our own JWT tokens
This post is associated to the video on my channel.
Let’s setup our apps on social media
- First, we have to setup our client app on social media, in our case we use Google and Facebook.
- Then we get our app secrets and ids. This process is simple, and well documented so, I just highlighted how to setup your redirect URLs properly in the video associated to this post.
- Then we create an ASP net core project and setup Cookie and JWT authentication. Adding Google and Facebook auth with the Credentials we saved earlier
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | services .AddAuthentication(options => { ///TODO: If you plan to use both cookie and JWT auth on this API, you can use this attribute ///[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddCookie(options => { options.Events.OnRedirectToLogin = options.Events.OnRedirectToAccessDenied = context => { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return Task.CompletedTask; }; }) .AddJwtBearer(cfg => { cfg.RequireHttpsMetadata = false; cfg.SaveToken = true; cfg.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = Configuration["JwtIssuer"], ValidAudience = Configuration["JwtIssuer"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])), ClockSkew = TimeSpan.Zero // remove delay of token when expire }; }) .AddFacebook(facebook => { facebook.AppId = Configuration["Authentication:Facebook:AppId"]; facebook.AppSecret = Configuration["Authentication:Facebook:AppSecret"]; facebook.SaveTokens = true; }) .AddGoogle(google => { google.ClientSecret = Configuration["Authentication:Google:ClientSecret"]; google.ClientId = Configuration["Authentication:Google:ClientId"]; google.SaveTokens = true; } |
Note: If you are using both Cookie and JWT auth scheme in your API, you can chose which scheme to use in your controllers using this attribute.
1 | [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] |
Social Authentication Process
The mobile authentication process is simple and will take place as follows:
- Xamarin Essentials calls our API passing the scheme (Facebook or Google in our case) depending on which social media the user wants to authenticate to.
- Our backend checks if the user is authenticated
1 | var auth = await Request.HttpContext.AuthenticateAsync(scheme); |
- If he is not, we start a challenge, redirecting the user to the social media’s authentication page. Xamarin Essentials handles this redirection on the client side for us.
1 | await Request.HttpContext.ChallengeAsync(scheme); |
Note: Once the challenge succeeds we are returned an access token. This token is not used to authenticate to our API. It is from the social media and we will use it later to get more user information.
- Then, once the user authenticates, we get his information using appropriate claims.
1 2 3 4 5 6 7 8 | var claims = auth.Principal.Identities.FirstOrDefault()?.Claims; var email = string.Empty; email = claims?.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Email)?.Value; var givenName = claims?.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.GivenName)?.Value; var surName = claims?.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Surname)?.Value; var nameIdentifier = claims?.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; |
- We then URL encode this user information and redirect. Xamarin Essentials will get the redirect URL and decode it to use in our app.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Get parameters to send back to the callback var qs = new Dictionary<string, string> { { "access_token", authToken.token }, { "refresh_token", string.Empty }, { "jwt_token_expires", authToken.expirySeconds.ToString() }, { "email", email }, { "firstName", givenName }, { "picture", picture }, { "secondName", surName }, }; // Build the result url var url = Callback + "://#" + string.Join( "&", qs.Where(kvp => !string.IsNullOrEmpty(kvp.Value) && kvp.Value != "-1") .Select(kvp => $"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}")); // Redirect to final url Request.HttpContext.Response.Redirect(url); |
Note that, the call back scheme above represents a scheme we will define in our mobile app we will name it: “xamarinapp”
Getting the User’s Picture
You might have noticed that the user’s picture is not passed to our backend even after authentication. We can get this with the access token returned from the social media, which we mentioned earlier, or add appropriate claims when setting up authentication.
- For Google, we will just add a little bit of configuration while setting up the Authentication in our startup.cs
1 2 3 4 5 6 7 8 9 | google.Scope.Add("profile"); google.Events.OnCreatingTicket = (context) => { var picture = context.User.GetProperty("picture").GetString(); context.Identity.AddClaim(new Claim("picture", picture)); return Task.CompletedTask; }; |
- For Facebook, we will use the access token to request the user’s profil picture. Here is how we do that.
1 2 3 4 5 6 7 8 | public async Task<string> GetFacebookProfilePicURL(string accessToken) { using var httpClient = new HttpClient(); var picUrl = $"https://graph.facebook.com/v5.0/me/picture?redirect=false&type=large&access_token={accessToken}"; var res = await httpClient.GetStringAsync(picUrl); var pic = JsonConvert.DeserializeAnonymousType(res, new { data = new PictureData() }); return pic.data.Url; } |
Adding JWT Auth
Now that we have our backend setup with social authentication, we need to handle the creation of tokens and managing users with ASPnet core identity. This is done easily. We will use mongodb as our database. But you could use sql server. The process is basically the same.
For this, we use this nugget package: AspNetCore.Identity.Mongo
- Then, we configure Aspnet core identity as follows:
1 2 3 4 5 6 7 8 | services.AddIdentityMongoDbProvider<AppUser, MongoRole>(identityOptions => { .... }, mongoIdentityOptions => { mongoIdentityOptions.ConnectionString = Configuration["IdentityDB"]; }).AddDefaultTokenProviders(); |
- When a user authenticates, we want to create the user in our database or get this user if he already exists. Here is how we do it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | private async Task CreateOrGetUser(AppUser appUser) { var user = await _userManager.FindByEmailAsync(appUser.Email); if (user == null) { //Create a username unique appUser.UserName = CreateUniqueUserName($"{appUser.FirstName} {appUser.SecondName}"); var result = await _userManager.CreateAsync(appUser); user = appUser; } await _signInManager.SignInAsync(user, true); } |
- When the user completes social authentication, we then create a JWT token for this user, with appropriate claims. This token will be used for authentication. Here is how we do it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | private (string token, double expirySeconds) GenerateJwtToken(AppUser user) { var issuedAt = DateTimeOffset.UtcNow; //Learn more about JWT claims at: https://tools.ietf.org/html/rfc7519#section-4 var claims = new List<Claim> { new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), //Subject, should be unique in this scope new Claim(JwtRegisteredClaimNames.Iat, //Issued at, when the token is issued issuedAt.ToUnixTimeMilliseconds().ToString(), ClaimValueTypes.Integer64), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), //Unique identifier for this specific token new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Email, user.Email) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var expires = issuedAt.AddMinutes(Convert.ToDouble(_configuration["JwtExpire"])); var expirySeconds = (long)TimeSpan.FromSeconds(Convert.ToDouble(_configuration["JwtExpire"])).TotalSeconds; var token = new JwtSecurityToken( _configuration["JwtIssuer"], _configuration["JwtIssuer"], claims, expires: expires.DateTime, signingCredentials: creds ); return (new JwtSecurityTokenHandler().WriteToken(token), expirySeconds); } |
Setting up Social Auth in the Mobile app
To setup JWT social auth with asp.net core and xamarin Essentials, we will have to create a Xamarin Forms project and add Xamarin Essentials. Then follow these steps.
- We configure the callback scheme on iOS in the infor.plist file :
- Then on Android we create a web authenticator activity with an Action View intent.
- We add a data scheme to this activity. This activity will be used during the authentication process by Xamarin Essentials.
1 2 3 4 5 | [Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable }, DataScheme = "xamarinapp")] public class WebAuthenticationCallbackActivity : Xamarin.Essentials.WebAuthenticatorCallbackActivity { } |
Note: The callback scheme should be the same as that on the backend. The value you chose for its name is case sensitive, as mentioned in the documentation.
Now that we have configured the platform projects, let’s get into the shared project. Here, we will invoke the web authenticator API, and let it do the job of handling redirects, calling web browser e.t.c.
- First, in our view model, we have two commands. One for each button, Signin with Facebook and Google.
1 2 3 | GoogleAuthCommand = new Command(async () => await OnAuthenticate("Google")); FacebookAuthCommand = new Command(async () => await OnAuthenticate("Facebook")); |
- Next, when we need to authenticate to our backend, we just call the web authenticator API, passing in the authentication URL and the callback scheme. Then that’s all.
1 2 3 4 | var authUrl = new Uri(AuthenticationUrl + scheme); var callbackUrl = new Uri("xamarinapp://"); var result = await WebAuthenticator.AuthenticateAsync(authUrl, callbackUrl); |
- When authentication succeeds, we can get the user’s information in the result returned.
1 2 3 4 | string authToken = result.AccessToken; string refreshToken = result.RefreshToken; var jwtTokenExpiresIn = result.Properties["jwt_token_expires"]; |
- Then using Shell navigation, we URL encode the user info and pass it to the User profile view model as follows.
1 2 3 4 5 6 7 8 9 10 | var userInfo = new Dictionary<string, string> { { "token", authToken }, { "name", $"{result.Properties["firstName"]} {result.Properties["secondName"]}"}, { "picture", HttpUtility.UrlDecode(result.Properties["picture"]) } }; var url = "UserProfil" + '?' + string.Join("&", userInfo.Select(kvp => $"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}")); await AppShell.Current.GoToAsync(url); |
- If the user cancels the authentication process, or closes the browser before it completes, a “TaskCanceledException” will be thrown, and we need to handle it.
Conclusion
With just a few lines of code, we made JWT social auth with ASP.net core and Xamarin Essentials functionality in our mobile app. There Microsoft’s MSAL library for more advanced authentication scenarios. But for small size mobile applications, this approach is good enough. Especially if you are willing to prototype quickly.
Adebiyi Emmanuel Oluwagbemiga
Damien Doumer
Damien Doumer
Andino Faturahman
Damien Doumer
iknowshaun
Damien Doumer
iknowshaun
Damien Doumer
iknowshaun
Damien Doumer