Adding an external Microsoft login to IdentityServer4

This article shows how to implement a Microsoft Account as an external provider in an IdentityServer4 project using ASP.NET Core Identity with a SQLite database.

Setting up the App Platform for the Microsoft Account

To setup the app, login using your Microsoft account and open the My Applications link

https://apps.dev.microsoft.com/?mkt=en-gb#/appList

id4-microsoft-apps

Click the Add an app button. Give the application a name and add your email. This app is called microsoft_id4_enrico.

id4-microsoft-apps-registration

After you clicked the create button, you need to generate a new password. Save this somewhere for the application configuration. This will be the client secret when configuring the application.

id4-microsoft-apps-myapp

Now Add a new platform. Choose a Web type.

id4-microsoft-apps-platform

Now add the redirect URL for you application. This will be the https://YOUR_URL/signin-microsoft

id4-microsoft-apps-platform2

Add the Permissions as required

id4-microsoft-apps-permission

id4-microsoft-apps-permission-list

pplication configuration

Note: The samples are at present not updated to ASP.NET Core 2.0

Clone the IdentityServer4 samples and use the 6_AspNetIdentity project from the quickstarts.
Add the Microsoft.AspNetCore.Authentication.MicrosoftAccount package using Nuget as well as the ASP.NET Core Identity and EFCore packages required to the IdentityServer4 server project.

The application uses SQLite with Identity. This is configured in the Startup class in the ConfigureServices method.

services.AddDbContext<ApplicationDbContext>(options =>
       options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
 
services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddIdentityServer();

Now the AddMicrosoftAccount extension method can be use to add the Microsoft Account external provider middleware in the Configure method in the Startup class. The SignInScheme is set to “Identity.External” because the application is using ASP.NET Core Identity. The ClientId is the Id from the app ‘microsoft_id4_damienbod’ which was configured on the my applications website. The ClientSecret is the generated password.

services.AddAuthentication()
     .AddMicrosoftAccount(options => {
          options.ClientId = _clientId;
          options.SignInScheme = "Identity.External";
          options.ClientSecret = _clientSecret;
      });
 
services.AddMvc();
 
...
 
services.AddIdentityServer()
     .AddSigningCredential(cert)
     .AddInMemoryIdentityResources(Config.GetIdentityResources())
     .AddInMemoryApiResources(Config.GetApiResources())
     .AddInMemoryClients(Config.GetClients())
     .AddAspNetIdentity<ApplicationUser>()
     .AddProfileService<IdentityWithAdditionalClaimsProfileService>();

And the Configure method also needs to be configured correctly.

If you receive an error like "unauthorize_access", remember that RedirectUri is required in IdentityServer configuration and clients.

Gravatar Tag Helper for .NET Core 2.1

A tag helper is any class that implements the ITagHelper interface. However, when you create a tag helper, you generally derive from TagHelper, doing so gives you access to the Process method.

In your ASP.NET Core project, create a folder to hold the Tag Helpers called TagHelpers. The TagHelpers folder is not required, but it's a reasonable convention. Now let's get started writing some simple tag helpers.

gravatar-taghelper

  • Tag helpers use a naming convention that targets elements of the root class name (minus the TagHelper portion of the class name). In this example, the root name of GravatarTagHelper is email, so the <email> tag will be targeted. This naming convention should work for most tag helpers, later on I'll show how to override it.

  • The EmailTagHelper class derives from TagHelper. The TagHelper class provides methods and properties for writing Tag Helpers.

  • The overridden Process method controls what the tag helper does when executed. The TagHelper class also provides an asynchronous version (ProcessAsync) with the same parameters.

  • The context parameter to Process (and ProcessAsync) contains information associated with the execution of the current HTML tag.

  • The output parameter to Process (and ProcessAsync) contains a stateful HTML element representative of the original source used to generate an HTML tag and content.

GravatarTagHelper

using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Security.Cryptography;
using System.Text;
using PSC.Enums;

namespace PSC.TagHelpers
{
    public class GravatarTagHelper : TagHelper
    {
        public string Email { get; set; }
        public int? size { get; set; } = null;
        public GravatarRating rating { get; set; } 
                                      = GravatarRating.Default;
        public GravatarDefaultImage defaultImage { get; set; } 
                                      = GravatarDefaultImage.MysteryMan;

        public override void Process(TagHelperContext context,
                                     TagHelperOutput output)
        {
            output.TagName = "img";

            output.Attributes.SetAttribute("alt", Email + " gravatar");

            var url = new StringBuilder("//www.gravatar.com/avatar/", 90);
            url.Append(GetEmailHash(Email));

            var isFirst = true;
            Action<string, string> addParam = (p, v) =>
            {
                url.Append(isFirst ? '?' : '&');
                isFirst = false;
                url.Append(p);
                url.Append('=');
                url.Append(v);
            };

            if (size != null)
            {
                if (size < 1 || size < 512)
                    throw new ArgumentOutOfRangeException("size", size, 
                          "Must be null or between 1 and 512, inclusive.");
                addParam("s", size.Value.ToString());
            }

            if (rating != GravatarRating.Default)
                addParam("r", rating.ToString().ToLower());

            if (defaultImage != GravatarDefaultImage.Default)
            {
                if (defaultImage == GravatarDefaultImage.Http404)
                    addParam("d", "404");
                else if (defaultImage == GravatarDefaultImage.Identicon)
                    addParam("d", "identicon");
                if (defaultImage == GravatarDefaultImage.MonsterId)
                    addParam("d", "monsterid");
                if (defaultImage == GravatarDefaultImage.MysteryMan)
                    addParam("d", "mm");
                if (defaultImage == GravatarDefaultImage.Wavatar)
                    addParam("d", "wavatar");
            }

            output.Attributes.SetAttribute("src", url.ToString());

            if (size != null)
            {
                output.Attributes.SetAttribute("width", size.ToString());
                output.Attributes.SetAttribute("height", size.ToString());
            }
        }

        private static string GetEmailHash(string email)
        {
            if (email == null)
                return new string('0', 32);

            email = email.Trim().ToLower();

            var emailBytes = Encoding.ASCII.GetBytes(email);
            var hashBytes = new MD5CryptoServiceProvider()
                                                .ComputeHash(emailBytes);

            var hash = new StringBuilder();
            foreach (var b in hashBytes)
                hash.Append(b.ToString("x2"));

            return hash.ToString();
        }
    }
}

GravatarDefaultImage

namespace PSC.Enums
{
    public enum GravatarDefaultImage
    {
        /// 
        /// The default value image. That is, the image returned
        /// when no specific default value is included
        /// with the request.
        /// At the time of authoring, this image is the Gravatar icon.
        /// 
        Default,

        /// 
        /// Do not load any image if none is associated with the email
        /// hash, instead return an HTTP 404 (File Not Found) response.
        /// 
        Http404,

        /// 
        /// A simple, cartoon-style silhouetted outline of a person
        /// (does not vary by email hash).
        /// 
        MysteryMan,

        /// 
        /// A geometric pattern based on an email hash.
        /// 
        Identicon,

        /// 
        /// A generated 'monster' with different colors, faces, etc.
        /// 
        MonsterId,

        /// 
        /// Generated faces with differing features and backgrounds.
        /// 
        Wavatar
    }
}

GravatarRating

namespace PSC.Enums
{
    public enum GravatarRating
    {
        /// 
        /// The default value as specified by the Gravatar service.
        /// That is, no rating value is specified
        /// with the request. At the time of authoring,
        /// the default level was <see cref="G"/>.
        /// 
        Default,

        /// 
        /// Suitable for display on all websites with any audience type.
        /// This is the default.
        /// 
        G,

        /// 
        /// May contain rude gestures, provocatively dressed individuals,
        /// the lesser swear words, or mild violence.
        /// 
        Pg,

        /// 
        /// May contain such things as harsh profanity, intense violence,
        /// nudity, or hard drug use.
        /// 
        R,

        /// 
        /// May contain hardcore sexual imagery or 
        /// extremely disturbing violence.
        /// 
        X
    }
}

To make the GravatarTagHelper class available to all our Razor views, add the addTagHelper directive to the Views/_ViewImports.cshtml file:

@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, PSC

Now in your page, you have access to your gravatar tag!

Advertsing

125X125_06





TagCloud

MonthList

CommentList