2

We have the following architecture:

  • .NET core 3.1 web application using razor pages, jQuery, etc as the UI (not an angular application)
  • .NET core 3.1 web api application serving as our api layer
  • Okta as our identity provider

I have implemented the Okta widget and middleware in the web application. Users can login, and after that happens I’m able to get a ClaimsPrincipal, access all of their scopes, and get to any custom profile data I’ve stored via open id. I'm able to secure views through the [Authorize] decoration. All that is working perfectly.

What I need to do now is implement the security checks on the API side. I’ve spent hours and hours looking at examples and have found many, but I'm either missing something obvious or what I'm doing is unique (and I can't imagine that what I'm doing is that unique). Basically what I need to do is:

  • Have the web app pass the auth and id tokens to the api
  • Have the api be able to verify the token and then decipher user information from the id token

This would then allow me to implement the necessary security logic on the API side. Let’s say its the API that returns customer orders - well I need to make sure that the user calling it is either an administrator or is the actual customer (so I don’t return customer data to someone who shouldn’t see it). I have all the role stuff figured out, I just can’t, for the life of me, figure out how to determine who someone is via the token?

Passing the tokens is pretty straightforward, but how would I get the token out of the ClaimsPrincipal object? Or do I need to call the Okta API after the user logs in to specifically get the access and id tokens?

Then of course I'll have to figure out how to get the API side to properly validate and parse the token that is sent.

If anyone could help me get started with this or point me in the right direction for an example, I would be very appreciative. At this point I have read every article on Owin, OpenID, Okta, authorization in .net core I could find.

Buckles
  • 161
  • 1
  • 9
  • 1
    Why do you need to parse the tokens? Shouldn't your middleware handle this and provide an identity with claims? – Cameron Tinker Jan 10 '20 at 18:38
  • 1
    I haven't integrated okta in core but the data you're looking for could/should be a claim and you should be able to access those through the ClaimsPrincipal. – ChiefTwoPencils Jan 10 '20 at 18:44
  • In the web app, yes, the middleware provides me with an Identity and its claims. However, I am not sure how to do that on the web api side. So I have to pass the token to web api for one (that is a reason I need the actual token), and then I need to validate it on the web api side. – Buckles Jan 10 '20 at 18:47
  • 1
    Your question could use a more descriptive title, one that describes the problem you're facing. Right now it's just the tags and doesn't describe anything. –  Jan 10 '20 at 18:53

2 Answers2

2

Thanks to Cameron Tinker's suggestion I was able to get this working. There were a few things that tripped me up, so I'll share them here in case anyone experiences the same.

If you're using Okta, you can do all of this through the Okta middleware package. You can do it just using the c# OpenID library, but the Okta.AspNetCore library will help things along.

First you register the middleware in the web app. Okta has lots of examples of this on their site and its pretty straightforward.

Within your web app you can use this to grab the token (after a user has authenticated of course)

await context.HttpContext?.GetTokenAsync("id_token")

Send that along in your API calls as part of the header using via the standard mechanism:

"Authorization" : "Bearer [token]"

On the Web API side, you use the same Okta.AspNetCore middleware package and can then decorate your controllers with [Authorize] to enforce auth on them. Here is where I got tripped up. If you are not using the default auth server in Okta and have setup a custom one for your application, you need to specific it and the audience in your config:

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme;
                options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme;
                options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme;
            })
            .AddOktaWebApi(new OktaWebApiOptions()
            {
                OktaDomain = oktaDomain,
                AuthorizationServerId = authServerId,
                Audience = clientId
            });

            services.AddAuthorization();

I had complete forgotten about the audience part - and with the way token validation works, that part is required.

From there, the middleware takes care of populating an ClaimsPrincipal for you, so you can access user information via the ClaimsPrincipal (HttpContext.User). I ended up creating a "CurrentUserService" and pulled it out into its own library so that I can consolidate all my auth handlers there; thereby allowing my web app and web api code to check permissions and retrieve information about the current user in the same way. That code is here if you're interested:

    public interface ICurrentUserService
    {
        public ClaimsPrincipal GetCurrentUser();

        public string GetCurrentUserDisplayName();

        public string GetCurrentUserFullName();

        public string GetCurrentUserId();

        public DateTime? GetCurrentUserDob();

        public string GetCurrentUserGender();

        public AddressFromClaimsDTO GetCurentUserAddress();

        public bool IsAuthenticated();
    }

    public class CurrentUserService : ICurrentUserService
    {

        private const string FULL_ADDRESS_CLAIM_TYPE = "address";

        private readonly IHttpContextAccessor _context;

        public CurrentUserService(IHttpContextAccessor context)
        {
            _context = context;
        }

        /// <summary>
        /// Gets whether or not the current user context is authenticated.
        /// </summary>
        /// <returns></returns>
        public bool IsAuthenticated()
        {
            return GetCurrentUser().Identity.IsAuthenticated;
        }

        /// <summary>
        /// Gets the current user's address.
        /// TODO: tie this into our address data model... but if addresses live in Okta what does that mean?
        /// </summary>
        /// <returns></returns>
        public AddressFromClaimsDTO GetCurentUserAddress()
        {
            var addressClaim = GetClaim(FULL_ADDRESS_CLAIM_TYPE);

            if (addressClaim != null)
            {
                //var parseValue = addressClaim.Value.ToString().Replace("{address:", "{\"address\":");
                var address = JsonSerializer.Deserialize<AddressFromClaimsDTO>(addressClaim.Value.ToString());
                return address;
            }
            else
            {
                return new AddressFromClaimsDTO();
            }
        }

        public ClaimsPrincipal GetCurrentUser()
        {
            return _context.HttpContext.User;
        }

        public string GetCurrentUserDisplayName()
        {
            return GetCurrentUser().Identity.Name;
        }

        public string GetCurrentUserFullName()
        {
            throw new NotImplementedException();
        }

        public string GetCurrentUserId()
        {
            throw new NotImplementedException();
        }

        public DateTime? GetCurrentUserDob()
        {
            var claim = GetClaim("birthdate");

            if (claim != null && !string.IsNullOrEmpty(claim.Value))
            {
                return DateTime.Parse(claim.Value);
            }
            else
            {
                return null;
            }
        }

        public string GetCurrentUserGender()
        {
            return GetClaim("gender")?.Value.ToString();
        }


        public Claim GetClaim(string claimType)
        {
            return _context.HttpContext.User.FindFirst(x => x.Type == claimType);
        }

    }
Buckles
  • 161
  • 1
  • 9
  • 2
    so you replaced access_token with id_token in bearer header? is that correct? don't you need the access_token to do proper access control/ resource authorization? – Adrian Nasui Jul 22 '20 at 06:52
0

Your ID Provider, Okta in this case, will issue an OpenID Connect authorization bearer token that you will need to pass along to any application that you want to protect.

On the Web Api side of your application, you will need to register your middleware for handling processing of Okta's OpenID Connect tokens. Then you can decorate your controllers/actions with [Authorize] and you can check an identity's claims.

Cameron Tinker
  • 9,634
  • 10
  • 46
  • 85
  • 2
    Thank you so much. I think this is getting me closer. So on the web app side, I can use HttpContext.GetTokenAsync("id_token") to grab the token, then pass that along as the authorization bearer token. Then on the web api side, I inject the middleware and it will essentially verify the token much like the web app does and then populate a ClaimsPrincipal for me (as I understand this example https://github.com/okta/samples-aspnetcore/tree/master/ASP.NET%20Core%203.x/resource-server) – Buckles Jan 12 '20 at 19:08
  • Cameron - Thanks again. As you can see below I got it working. One thing I have noticed is that the claims on the web api side are, for lack of a better word, incomplete. The address claim, for example, is not there. Do you have any ideas on that? – Buckles Jan 13 '20 at 14:59
  • I would make sure that you are returning this claim from Okta. It should be there in your ClaimsPrincipal that is deserialized from your authorization token. – Cameron Tinker Jan 13 '20 at 16:34
  • Good call and I made sure I am returning the claim and verified that in the web application. So the claims are there in the web app. However, the very same id_token, when passed over to the api project, has claims missing once its deserialized. I do not know if it is because I am using the Okta middleware and according to this (at the bottom) it may purposely not include everything? https://developer.okta.com/quickstart-fragments/dotnet/aspnetcore-implicit/#protect-application-resources . – Buckles Jan 13 '20 at 20:55
  • ... the Okta help page says: The Okta middleware automatically validates tokens and populates HttpContext.User with a limited set of user information. If you want to do more with the user, you can use the Okta .NET SDK to get or update the user's details stored in Okta. ----- that makes me wonder if I should just scrap their middleware and use the MS OpenIdConnect library to handle it (maybe then I can specify the scopes I want it to deserialize?), or if I should just suck it up and call back to the Okta API to get the additional data I need. – Buckles Jan 13 '20 at 20:57
  • If it's not included in the JWT returned from Okta, there is likely another method to get this information. I would consult their documentation for more information. – Cameron Tinker Jan 13 '20 at 21:52