简体   繁体   English

401 在 Xamarin.Forms 上未经 Azure b2c 授权

[英]401 Unauthorized with Azure b2c on Xamarin.Forms

I have a Xamarin.Forms application that I'm using to connect to an App Service backend, and I'm attempting to authenticate using Auzre B2C JWT tokens.我有一个 Xamarin.Forms 应用程序,用于连接到应用服务后端,我正在尝试使用 Auzre B2C JWT 令牌进行身份验证。

Through various tutorials I have managed to get B2C setup using microsoft accounts, and I am able to create users, change passwords, and generate access tokens.通过各种教程,我设法使用微软帐户进行 B2C 设置,并且我能够创建用户、更改密码和生成访问令牌。

My next step was to add the [Authorize] attribute to my controller and attempt to pass that token to my app service and authorize users, but no matter what I try I get a 401 Unauthorized response from my service.我的下一步是将 [Authorize] 属性添加到我的控制器并尝试将该令牌传递给我的应用程序服务并授权用户,但无论我尝试什么,我的服务都会收到 401 Unauthorized 响应。

  • I'm adding the JWT token to the Authorization header of my HttpClient, and it's getting to the service.我正在将 JWT 令牌添加到我的 HttpClient 的 Authorization 标头中,并且它正在访问服务。
  • I can paste my token into https://jwt.ms/ , and it correctly tells me what's in my token.我可以将我的令牌粘贴到https://jwt.ms/ 中,它会正确地告诉我令牌中的内容。
  • I've implemented this code in an attempt to figure out what's wrong.我已经实现了这段代码,试图找出问题所在。

ConfigureServices in startup.cs looks like this: startup.cs 中的 ConfigureServices 如下所示:

public void ConfigureServices(IServiceCollection services) {

            services.AddAuthentication(options => {                
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
                .AddJwtBearer(options => {

                    options.Audience = Configuration["Authentication:AzureAd:ClientId"];

                    options.Events = new JwtBearerEvents {
                        OnAuthenticationFailed = AuthenticationFailed
                    };

                    options.Authority = $"https://{tenant name}.b2clogin.com/{tenant id}/{Configuration["Authentication:AzureAd:Policy"]}";

                    options.Events = new JwtBearerEvents {

                        OnAuthenticationFailed = ctx =>
                        {
                            ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
                            message += "From OnAuthenticationFailed:\n";
                            message += FlattenException(ctx.Exception);
                            return Task.CompletedTask;
                        },
                        OnChallenge = ctx =>
                        {
                            message += "From OnChallenge:\n";
                            ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
                            ctx.Response.ContentType = "text/plain";
                            return ctx.Response.WriteAsync(message);
                        },
                        OnMessageReceived = ctx =>
                        {
                            message = "From OnMessageReceived:\n";
                            ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken);
                            if (BearerToken.Count == 0)
                                BearerToken = "no Bearer token sent\n";
                            message += "Authorization Header sent: " + BearerToken + "\n";
                            return Task.CompletedTask;
                        },
                        OnTokenValidated = ctx =>
                        {
                            Debug.WriteLine("token: " + ctx.SecurityToken.ToString());
                            return Task.CompletedTask;
                        }
                    };

                });

            services.AddMvc();
        }

Configure looks like this:配置看起来像这样:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
            if (env.IsDevelopment()) {
                app.UseDeveloperExceptionPage();

                IdentityModelEventSource.ShowPII = true;
            } else {
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseAuthentication();
            app.UseMvc();
        }

And I've also added this call to AuthenticationFailed, so I'll know if my authentication is working or not:而且我还将此调用添加到 AuthenticationFailed,因此我会知道我的身份验证是否有效:


        Task AuthenticationFailed(AuthenticationFailedContext arg) {
            Console.WriteLine(arg.Exception.Message);
            return Task.FromResult(0);
        }

With my current setup I'm getting a 401 error from the server, and that's right after it hits the OnChallenge event wired up in Startup.cs.使用我当前的设置,我从服务器收到 401 错误,这是在它击中 Startup.cs 中连接的 OnChallenge 事件之后。 According to the link above, that's what gets called right before it returns a 401 to the user, so it seems like the service is receiving the proper token, and authenticating, but maybe I don't have the correct rights set up?根据上面的链接,这就是在向用户返回 401 之前调用的内容,因此服务似乎正在接收正确的令牌并进行身份验证,但也许我没有设置正确的权限?

I'm not sure where to go from here, but any guidance would be appreciated.我不确定从哪里开始,但任何指导将不胜感激。

Edit:编辑:

As mentioned in a comment below, I was able to curl my website using the access token generated after logging in through my app like this:正如下面的评论中提到的,我能够使用通过我的应用程序登录后生成的访问令牌来卷曲我的网站,如下所示:

curl https://mywebsite.azurewebsites.net/api/Values -i --header "Authorization: Bearer [TOKEN]" curl https://mywebsite.azurewebsites.net/api/Values -i --header "Authorization: Bearer [TOKEN]"

And that seems to work with no issue, so it seems like it's something with how I'm making a call to the controller through my app, not the authentication itself.这似乎没有问题,所以这似乎与我如何通过我的应用程序调用控制器有关,而不是身份验证本身。

Edit 2 (solution):编辑2(解决方案):

So, as per Edit 1, I was correct in that it was just how I was adding the token to the authorization header.因此,根据 Edit 1,我是正确的,因为这正是我将令牌添加到授权标头的方式。 It wasn't my brightest moment, but I wasn't calling .Value on the claim that contained my Access Token.这不是我最辉煌的时刻,但我并没有针对包含我的访问令牌的声明调用 .Value。 I was only calling .ToString() on the claim itself, so the "token" was actually the entire claim text "Access Token: ".我只是在声明本身上调用 .ToString(),所以“令牌”实际上是整个声明文本“访问令牌:”。 I didn't think much of it at the time when I was debugging my service, because I didn't realize it shouldn't have that text there.在调试我的服务时,我并没有想太多,因为我没有意识到它不应该有那个文本。

Once I corrected that issue the service started working as expected.一旦我纠正了这个问题,服务就开始按预期工作。

So, in the end, I guess it was all working as expected.所以,最后,我想这一切都按预期进行。 I was, in fact, not sending the expected token, so I was ... unauthorized.事实上,我没有发送预期的令牌,所以我......未经授权。

As requested the line of code that I had to change was this:根据要求,我必须更改的代码行是这样的:

So, this won't be 100% applicable to most because I'm using a business library called CSLA, but the idea is the same regardless.因此,这不会 100% 适用于大多数人,因为我使用的是一个名为 CSLA 的业务库,但无论如何这个想法都是一样的。

After my b2c call returns the token I store it in the ApplicationContext.User.Identity that's built into the CSLA library.在我的 b2c 调用返回令牌后,我将它存储在 CSLA 库中内置的 ApplicationContext.User.Identity 中。 That allows me to get the access token claim later.这允许我稍后获得访问令牌声明。 The important part to take away from this is that I'm storing the token some place that I can access it later when I want to add it to the authorization header.重要的部分是我将令牌存储在某个地方,以便稍后我想将其添加到授权标头时可以访问它。

Later, when I'm making the call with my httpclient I need to get that token, so originally, I was doing this:后来,当我使用 httpclient 进行调用时,我需要获取该令牌,因此最初我是这样做的:

httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ((ClaimsIdentity)ApplicationContext.User.Identity).Claims.FirstOrDefault(c => c.Type == "AccessToken") .ToString() ); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ((ClaimsIdentity)ApplicationContext.User.Identity).Claims.FirstOrDefault(c => c.Type == "AccessToken") .ToString() );

This isn't correct.这不正确。 This was sending the "token" as with value "Access Token: [token value]. Essentially, it was adding the words "Access Token" to the token I needed to authenticate, and that was failing, because the words "Access Token" are not actually supposed to be part of the token you use to authenticate.这是发送“令牌”作为值“访问令牌:[令牌值]。本质上,它是将“访问令牌”字样添加到我需要进行身份验证的令牌中,但失败了,因为“访问令牌”字样实际上不应该是您用于身份验证的令牌的一部分。

After I changed my call to this:在我改变我的电话之后:

httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ((ClaimsIdentity)ApplicationContext.User.Identity).Claims.FirstOrDefault(c => c.Type == "AccessToken") .Value ); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ((ClaimsIdentity)ApplicationContext.User.Identity).Claims.FirstOrDefault(c => c.Type == "AccessToken") .Value );

It started getting only the token value, and when that was added to the authorization header, it worked just fine.它开始只获取令牌值,当它被添加到授权标头时,它工作得很好。

Edit 2 explains the answer to my problem.编辑 2 解释了我的问题的答案。

I wasn't adding the token correctly to the authorization header, so the service wasn't able to authenticate the token, or rather, it saw the token as invalid.我没有将令牌正确添加到授权标头中,因此该服务无法对令牌进行身份验证,或者更确切地说,它认为令牌无效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM