Authenticating Users with .NET

.NET makes it easy to serve a web app to many authenticated users. By using Google Identity Platform, you can easily access information about your users while ensuring that their sign-in credentials are safely managed by Google. OAuth 2.0 makes it easy to provide a sign-in flow for all users of your app and provides your application with access to basic profile information about authenticated users.

The sample code in the Bookshelf app provides an example of how to create a sign-in flow for users and how to use profile information to provide users with personalized functionality.

About ASP.NET authentication

Before your application can authenticate users, you need a way to store information about the current user.

ASP.NET MVC includes authentication backed by encrypted cookies for storing information about the current user. Cookie authentication can be configured to use an external sign-in provider such as Google OAuth 2.0.

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ExternalCookie
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

Authenticating users

Authenticating the user involves two basic steps, which together are called the web service flow. The steps are:

  • Redirect the user to Google’s authorization service.
  • Process the response when Google redirects the user back to your application.

The Microsoft.Owin.Security.Google package makes it easy to integrate OAuth2 into your application without having to implement the flow yourself.

  1. Configure Google OAuth 2.0 authentication options with the client ID and client secret:

    var authenticationOptions = new GoogleOAuth2AuthenticationOptions()
    {
        ClientId = LibUnityConfig.GetConfigVariable("GoogleCloudSamples:AuthClientId"),
        ClientSecret = LibUnityConfig.GetConfigVariable("GoogleCloudSamples:AuthClientSecret"),
    };
    

  2. Configure scopes for Google authorization service.

    Your application will send the user to Google’s authorization service. The application generates the URL by using your Client ID and the scopes that the application uses. Scopes indicate which items of the user’s information that your application is allowed to access. Within Google, there are multiple services, with different scopes for each service. For example, there is a scope that allows read-only access to Google Drive and another scope that allows read-write access. This sample requires only the basic profile scope, which grants the application access to the user's basic profile information:

    // Add scope to access user's basic profile information
    authenticationOptions.Scope.Add("profile");
    

  3. Redirect to Google's user consent screen to sign in, and redirect back to the application's home page:

    public void Login()
    {
        // Redirect to the Google OAuth 2.0 user consent screen
        HttpContext.GetOwinContext().Authentication.Challenge(
            new AuthenticationProperties { RedirectUri = "/" },
            "Google"
        );
    }
    

  4. Process the authorization response.

    Google sends the user back to your application along with information about the user. By default, Microsoft.Owin.Security.Google signs in the authenticated user and provide access to the user's name and unique identifier. To access the user's profile image URL, the OnAuthenticated callback reads the user's profile image URL from the Google OAuth response and adds it to the current user identity:

    // After OAuth authentication completes successfully,
    // read user's profile image URL from the profile
    // response data and add it to the current user identity
    OnAuthenticated = context =>
    {
        var profileUrl = context.User["image"]["url"].ToString();
        context.Identity.AddClaim(new Claim(ClaimTypes.Uri, profileUrl));
        return Task.FromResult(0);
    }
    

    As a result, the user's profile image URL is accessible via the application's User class:

        public class User : ClaimsPrincipal
        {
            public User(IPrincipal principal) : base(principal as ClaimsPrincipal) { }
    
            public string Name => this.Identity.Name;
            public string UserId => this.FindFirst(ClaimTypes.NameIdentifier).Value;
            public string ProfileImage => this.FindFirst(ClaimTypes.Uri).Value;
        }
    

  5. Create a way for users to sign out:

    public ActionResult Logout()
    {
        Request.GetOwinContext().Authentication.SignOut();
        return Redirect("/");
    }
    

  6. Use the profile information provided by the current user in your templates.

    For the MVC application views to access details about the currently signed-in user user, the application's page base type is configured to use BookshelfWebViewPage, a base view page that exposes the current user:

    <pages pageBaseType="GoogleCloudSamples.Views.BookshelfWebViewPage">
    

    public abstract class BookshelfWebViewPage<TModel> : WebViewPage<TModel>
    {
        public User CurrentUser => new User(this.User);
    }
    

  7. Update the layout view to indicate to the user that they are signed in or signed out:

    <p class="navbar-text navbar-right">
        @if (Request.IsAuthenticated)
        {
            <img src="@CurrentUser.ProfileImage" class="img-circle" width="24">
            <span>
                @CurrentUser.Name
                &nbsp;
                @Html.ActionLink("(logout)", "Logout", "Session")
            </span>
        }
        else
        {
            @Html.ActionLink("Login", "Login", "Session")
        }
    </p>
    

Personalization

Now that you have the signed-in user's profile information available, you can keep track of which user added which book to the database:

        public async Task<ActionResult> Create(Book book, HttpPostedFileBase image)
        {
            if (ModelState.IsValid)
            {
                if (Request.IsAuthenticated)
                {
                    // Track the user who created this book
                    book.CreatedById = CurrentUser.UserId;
                }

                _store.Create(book);

                // ...

Now that you have that information in the database, you can use it to create a new view that allows a user to see all the books that they have added:

public ActionResult Mine(string nextPageToken)
{
    if (Request.IsAuthenticated)
    {
        return View("Index", new ViewModels.Books.Index()
        {
            // Fetch books created by the logged in user
            BookList = _store.List(_pageSize, nextPageToken, userId: CurrentUser.UserId)
        });
    }
    else
    {
        return RedirectToAction("Index");
    }
}

Datastore

public BookList List(int pageSize, string nextPageToken, string userId = null)
{
    var query = new Query("Book") { Limit = pageSize };
    if (userId != null)
        query.Filter = Filter.Equal("CreatedById", userId);
    if (!string.IsNullOrWhiteSpace(nextPageToken))
        query.StartCursor = ByteString.FromBase64(nextPageToken);
    var results = _db.RunQuery(query);
    return new BookList()
    {
        Books = results.Entities.Select(entity => entity.ToBook()),
        NextPageToken = results.Entities.Count == query.Limit ?
            results.EndCursor.ToBase64() : null
    };
}

Cloud SQL

public BookList List(int pageSize, string nextPageToken, string userId = null)
{
    IQueryable<Book> query = _dbcontext.Books.OrderBy(book => book.Id);
    if (userId != null)
    {
        // Query for books created by the user
        query = query.Where(book => book.CreatedById == userId);
    }
    if (nextPageToken != null)
    {
        long previousBookId = long.Parse(nextPageToken);
        query = query.Where(book => book.Id > previousBookId);
    }
    var books = query.Take(pageSize).ToArray();
    return new BookList()
    {
        Books = books,
        NextPageToken = books.Count() == pageSize ? books.Last().Id.ToString() : null
    };
}

Monitor your resources on the go

Get the Google Cloud Console app to help you manage your projects.

Send feedback about...