How to pass/verify Open ID token between .net core web app and web api?

霸气de小男生 提交于 2020-01-26 03:57:08

问题


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.


回答1:


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.




回答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);
        }

    }


来源:https://stackoverflow.com/questions/59687103/how-to-pass-verify-open-id-token-between-net-core-web-app-and-web-api

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!