How we can authenticate signalR by using cross domain call? - api

I am working asp.net apis project where i am using signalR and i want to authenticate with authorize attribute.For this i send jwt token and this token add
into signalR pipeline.First i call to Apis to get token.Then i send this token to get signalR connection,But signalR returns unauthorized response.
The below is my code,
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
// SignalR Auth0 custom configuration.
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerAuthenticationProvider()
{
OnRequestToken = context =>
{
if (context.Request.Path.Value.StartsWith("/signalr"))
{
string bearerToken = context.Request.Query.Get("access_token");
if (bearerToken != null)
{
string[] authorization = new string[] { "bearer " + bearerToken };
context.Request.Headers.Add("Authorization", authorization);
}
}
return null;
}
}
});
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
});
app.UseWebApi(config);
GlobalHost.DependencyResolver.Register(
typeof(SignalRHUB),
() => new SignalRHUB(new UnitOfWork(new DbFactory())));
//GlobalHost.HubPipeline.RequireAuthentication();
//app.MapSignalR();

Related

Fetch data return Untheorized 401 access to asp.net core API protected in Azure AD

Im new to `webassembly blazor, Im spend too much time trying to figure out what's wrong here but I couldnt manage.
I have the following scenario:
Asp.net API registered and protected in Azure AD
Expose API with Scope AcessApi with status enabled
A Client application is added to authorized client applications
Token configuration both are checked Access Token and ID Token
And a client app that will call the API, developed in webassembly blazor
client app is registered in Azure AD
Client API permissions has delegated permission to use my client API
with correct scope AccessApi.
I tested the API using swagger interface, it forces user to authenticate first before accessing the API.
I tested using curl and grabbed the token from swagger interface and works perfectly fine.
curl -X GET "http://localhost:9400/api/getdata" -H "accept: text/plain" -H "Authorization: Bearer XX"
However, when my client application trying to access the API, a sign-in page pop-up for credentials, I could see the Token ID at browser bar being retrieved and while calling the API the app logs error not authorized
program class of the client application:
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
//builder.Logging.SetMinimumLevel(LogLevel.Debug);
////builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient("AccessApi",
client => client.BaseAddress = new Uri("http://localhost:9400"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("AccessApi"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
});
await builder.Build().RunAsync();
}
in CustomAuthorizationMessageHandler class I have defined:
private static string scope = #"api://xxx-35fc2470889f/AccessApi";
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "http://localhost:9400" },
}
In appsettings.json a defined the client id of the API and tenant id without scopes since they are been defined in the CustomAuthorizationMessageHandlerclass:
{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/<tenant_id>",
"ClientId": "<clientid>",
"CallbackPath": "/signin-oidc",
"ValidateAuthority": "true"
}
}
After a successful login via Azure AD, I call to fetch data from the API here
protected override async Task OnInitializedAsync()
{
...
try
{
responseBody = await Http.GetStringAsync("/api/getdata"); # use base URL of the API
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
}
the console logs
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[1]
Authorization was successful.
info: System.Net.Http.HttpClient.AccessApi.ClientHandler[100]
Sending HTTP request GET http://localhost:9400/api/getdata
:9400/customer-manager/api/getdata:1 Failed to load resource: the server responded with a status of 401 (Unauthorized)
What could be wrong here?
Is there a way how to print the return token?
Update
I tested the API using Postman where auth Grant type is Implicit, after successful login, I store token on variable and passed in the header as Bearer the API return 401 Unauthroized. I decoded the token it contains the right scope AccessApi , with the correct clientId. what could be wrong here ?
If you want to call Microsoft graph and your custom API in one blazor webassembly project, we can implement it by creating different HTTP client to call different API
For example
Register a server API app
Register an AAD app for the Server API app
Expose an API
Register a client app
Register a client app
Enable Implicit grant flow
Add API permissions. ( API app permissions)
Configure API app
Please add the following code in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder => builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.Authority += "/v2.0";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuers = new[] {
$"https://sts.windows.net/{Configuration["AzureAD:TenantId"]}/",
$"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/v2.0"
},
RoleClaimType = "roles",
// The web API accepts as audiences both the Client ID (options.Audience) and api://{ClientID}.
ValidAudiences = new[]
{
options.Audience,
$"api://{options.Audience}"
}
};
});
....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.OAuthClientId(Configuration["Swagger:ClientId"]);
c.OAuthScopeSeparator(" ");
c.OAuthAppName("Protected Api");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Configure Client APP
Create custom AuthorizationMessageHandler for Graph API and custom API
// custom API
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "<your web API url>" },
scopes: new[] { "the API app scope" });
}
}
Add the following code to the program.cs
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
// register HTTP client to call our own api
builder.Services.AddHttpClient("MyAPI", client => client.BaseAddress = new Uri("<your web API url>"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("<the API app scope>");
});
await builder.Build().RunAsync();
}
}
Call the api
#inject IHttpClientFactory _clientFactory
var httpClient = _clientFactory.CreateClient("<the client name you register>");
await apiClient.GetStringAsync("path");
Finally I found the issue was on the server side ASP.net core where I was validating the token in ConfigureServices at startup class:
// For token parameters validation
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.Audience = "<xx>"; // Application id
o.Authority = "https://login.microsoftonline.com/<xx>"; // Tenant ID
//Token validation
o.TokenValidationParameters = new TokenValidationParameters {ValidateIssuerSigningKey = false, ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true};
});
I had to disable Issuer since the token is coming from a different application.

Blazor WebAssembly SignalR Authentication

I would love to see an example on how to add authentication to a SignalR hub connection using the WebAssembly flavor of Blazor. My dotnet version is 3.1.300.
I can follow these steps to get an open, unauthenticated SignalR connection working: https://learn.microsoft.com/en-us/aspnet/core/tutorials/signalr-blazor-webassembly?view=aspnetcore-3.1&tabs=visual-studio
All the tutorials I find seem older or are for a server-hosted type, and don't use the built-in template.
I have added authentication to the rest of the back-end, using the appropriate template and these instructions, including the database:
https://learn.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1
But every time I add [Authenticate] to the chat hub, I get an error returned. Is there any way, extending the first tutorial, that we can authenticate the hub that is created there? It would be great to hitch on to the built-in ASP.NET system, but I am fine just passing a token in as an additional parameter and doing it myself, if that is best. In that case I would need to learn how to get the token out of the Blazor WebAssembly, and then look it up somewhere on the server. This seems wrong, but it would basically fill my needs, as an alternative.
There are all sorts of half-solutions out there, or designed for an older version, but nothing to build off the stock tutorial that MS presents.
Update:
Following the hints in this news release https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-2-release-now-available/, I now can get a token from inside the razor page, and inject it into the header. I guess this is good?? But then how do I get it and make use of it on the server?
Here is a snippet of the razor code:
protected override async Task OnInitializedAsync()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(UriHelper.BaseUri);
var tokenResult = await AuthenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Value}");
hubConnection = new HubConnectionBuilder()
.WithUrl(UriHelper.ToAbsoluteUri("/chatHub"), options =>
{
options.AccessTokenProvider = () => Task.FromResult(token.Value);
})
.Build();
}
}
Update 2:
I tried the tip in here: https://github.com/dotnet/aspnetcore/issues/18697
And changed my code to:
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chatHub?access_token=" + token.Value))
.Build();
But no joy.
I've come across the same issue.
My solution was 2-sided: I had to fix something in the fronend and in the backend.
Blazor
In your connection builder you should add the AccessTokenProvider:
string accessToken = "eyYourToken";
connection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/hub/chat", options =>
{
options.AccessTokenProvider = () => Task.FromResult(token.Value);
})
.Build();
options.AccessTokenProvider is of type Func<Task<string>>, thus you can also perform async operations here. Should that be required.
Doing solely this, should allow SignalR to work.
Backend
However! You might still see an error when SignalR attempts to create a WebSocket connection. This is because you are likely using IdentityServer on the backend and this does not support Jwt tokens from query strings. Unfortunately SignalR attempts to authorize websocket requests by a query string parameter called access_token.
Add this code to your startup:
.AddJwtBearer("Bearer", options =>
{
// other configurations omitted for brevity
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs"))) // Ensure that this path is the same as yours!
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
edit 1: Clarified the usage of the Blazor SignalR code
In my case (Blazor WebAssembly, hosted on ASP.NET Core 5.0 using JWT Bearer Token Auth), I had to add the following:
Blazor WASM Client
When building the connection (in my case: in the constructor of some service proxy class), use IAccessTokenProvider and configure the AccessTokenProvider option like so:
public ServiceProxy(HttpClient httpClient, IAccessTokenProvider tokenProvider) {
HubConnection = new HubConnectionBuilder()
.WithUrl(
new Uri(httpClient.BaseAddress, "/hubs/service"),
options => {
options.AccessTokenProvider = async () => {
var result = await tokenProvider.RequestAccessToken();
if (result.TryGetToken(out var token)) {
return token.Value;
}
else {
return string.Empty;
}
};
})
.WithAutomaticReconnect() // optional
.Build();
}
ASP.NET Core Server
Add the following to Startup.ConfigureServices:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => {
// store user's "name" claim in User.Identity.Name
options.TokenValidationParameters.NameClaimType = "name";
// pass JWT bearer token to SignalR connection context
// (from https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0)
options.Events = new JwtBearerEvents {
OnMessageReceived = context => {
var accessToken = context.Request.Query["access_token"];
// If the request is for on of our SignalR hubs ...
if (!string.IsNullOrEmpty(accessToken) &&
(context.HttpContext.Request.Path.StartsWithSegments("/hubs/service"))) {
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
This is my solution and works
[Inject] HttpClient httpClient { get; set; }
[Inject] IAccessTokenProvider tokenProvider { get; set; }
HubConnection hubConnection { get; set; }
(...)
private async Task ConnectToNotificationHub()
{
string url = httpClient.BaseAddress.ToString() + "notificationhub";
var tokenResult = await tokenProvider.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
hubConnection = new HubConnectionBuilder().WithUrl(url, options =>
{
options.Headers.Add("Authorization", $"Bearer {token.Value}");
}).Build();
await hubConnection.StartAsync();
hubConnection.Closed += async (s) =>
{
await hubConnection.StartAsync();
};
hubConnection.On<string>("notification", m =>
{
string msg = m;
});
}
}

How do I acquire the right token for a Web API from within an Azure Function App or Javascript?

We have a web service which requires authentication before use. When you type in the URL of the Web Service directly in the browser, everything works fine. However, if you were to try and call this very same service from Javascript, it doesn't work because authentication has yet to happen.
I've tried calling getAccessTokenAsync (this is part of the OfficeJS libray) but ended up getting one of those 1300x errors. Also, since this call is still in Preview I would like to avoid it.
The code below gets invoked when you enter the URL to the webservice directly in the browser windows. You're authenticated and everything works fine.
I just don't know how to do the equivalent authentication from within an Azure Function App, or Javascript (from a Web-Add-In)
public partial class AuthStartup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
// This part is for web sso so web pages can consume the API without obtaining a token
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
// http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.Whr = "ourcompany.com";// similar effect to domain_hint from client so users never see the "choose account" prompt
return Task.FromResult(0);
}
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataAddress"],
Wtrealm = ConfigurationManager.AppSettings["ida:Audience"],
// this part is needed so that cookie and token auth can coexist
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = new string[] { $"spn:{ConfigurationManager.AppSettings["ida:Audience"]}" }
}
});
// This part is for bearer token authentication
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataAddress"],
});
}
}

Could not complete oAuth2.0 login

I have implemented Aspnet.security.openidconnect.server with .net core 2.1 app. Now I want to test my authorization and for that I am making postman request. If I change the grant type to client_credentials then it works but I want to test complete flow, so I select grant type to Authorzation code and it starts giving error "Could not complete oAuth2.0 login.
Here is the code:
services.AddAuthentication(OAuthValidationDefaults.AuthenticationScheme).AddOAuthValidation()
.AddOpenIdConnectServer(options =>
{
options.AuthorizationEndpointPath = new PathString(AuthorizePath);
// Enable the token endpoint.
options.TokenEndpointPath = new PathString(TokenPath);
options.ApplicationCanDisplayErrors = true;
options.AccessTokenLifetime = TimeSpan.FromMinutes(5);
#if DEBUG
options.AllowInsecureHttp = true;
#endif
options.Provider.OnValidateAuthorizationRequest = context =>
{
if (string.Equals(context.ClientId, Configuration["OpenIdServer:ClientId"], StringComparison.Ordinal))
{
context.Validate(context.RedirectUri);
}
return Task.CompletedTask;
};
// Implement OnValidateTokenRequest to support flows using the token endpoint.
options.Provider.OnValidateTokenRequest = context =>
{
// Reject token requests that don't use grant_type=password or grant_type=refresh_token.
if (!context.Request.IsClientCredentialsGrantType() && !context.Request.IsPasswordGrantType()
&& !context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
description: "Only grant_type=password and refresh_token " +
"requests are accepted by this server.");
return Task.CompletedTask;
}
if (string.IsNullOrEmpty(context.ClientId))
{
context.Skip();
return Task.CompletedTask;
}
if (string.Equals(context.ClientId, Configuration["OpenIdServer:ClientId"], StringComparison.Ordinal) &&
string.Equals(context.ClientSecret, Configuration["OpenIdServer:ClientSecret"], StringComparison.Ordinal))
{
context.Validate();
}
return Task.CompletedTask;
};
// Implement OnHandleTokenRequest to support token requests.
options.Provider.OnHandleTokenRequest = context =>
{
// Only handle grant_type=password token requests and let
// the OpenID Connect server handle the other grant types.
if (context.Request.IsClientCredentialsGrantType() || context.Request.IsPasswordGrantType())
{
//var identity = new ClaimsIdentity(context.Scheme.Name,
// OpenIdConnectConstants.Claims.Name,
// OpenIdConnectConstants.Claims.Role);
ClaimsIdentity identity = null;
if (context.Request.IsClientCredentialsGrantType())
{
identity = new ClaimsIdentity(new GenericIdentity(context.Request.ClientId, "Bearer"), context.Request.GetScopes().Select(x => new Claim("urn:oauth:scope", x)));
}
else if (context.Request.IsPasswordGrantType())
{
identity = new ClaimsIdentity(new GenericIdentity(context.Request.Username, "Bearer"), context.Request.GetScopes().Select(x => new Claim("urn:oauth:scope", x)));
}
// Add the mandatory subject/user identifier claim.
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
// By default, claims are not serialized in the access/identity tokens.
// Use the overload taking a "destinations" parameter to make sure
// your claims are correctly inserted in the appropriate tokens.
identity.AddClaim("urn:customclaim", "value",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
var ticket = new Microsoft.AspNetCore.Authentication.AuthenticationTicket(
new ClaimsPrincipal(identity),
new Microsoft.AspNetCore.Authentication.AuthenticationProperties(),
context.Scheme.Name);
// Call SetScopes with the list of scopes you want to grant
// (specify offline_access to issue a refresh token).
ticket.SetScopes(
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess);
context.Validate(ticket);
}
return Task.CompletedTask;
};
and here is the postman collection:
Now I am not sure that whether the issue is in my code or in postman collection? I think the callback url is creating some issue but I am not sure. Any help?
Update:
By visiing this page https://kevinchalet.com/2016/07/13/creating-your-own-openid-connect-server-with-asos-implementing-the-authorization-code-and-implicit-flows/ I have found the issue. I haven't handled authorization code flow in my code but I even don't want to. Is there any way I test my code with Resource owner password? I can't see this grant type in request form. In simple words I want postman to open login screen which is in Controller/Login/Index and I select my ssl Certificate and it generates a token for me?
hello i think that you have to add https://www.getpostman.com/oauth2/callback as the redirect_url in your server config, i don't think that your STS server will return tokens back to a non trusted url. that's why it works from your app but not from Postman

Azure mobile apps Custom + Facebook authentication with Xamarin.Forms

I'm working on a Xamarin Forms mobile app with .NET backend. I followed this guide and successfully set up custom authentications with one change in Startup.cs:
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
Without "if (string.IsNullOrEmpty(settings.HostName))". Otherwise I am always getting unauthorized for all requests after login.
Server project:
Auth controller
public class ClubrAuthController : ApiController
{
private readonly ClubrContext dbContext;
private readonly ILoggerService loggerService;
public ClubrAuthController(ILoggerService loggerService)
{
this.loggerService = loggerService;
dbContext = new ClubrContext();
}
public async Task<IHttpActionResult> Post(LoginRequest loginRequest)
{
var user = await dbContext.Users.FirstOrDefaultAsync(x => x.Email == loginRequest.username);
if (user == null)
{
user = await CreateUser(loginRequest);
}
var token = GetAuthenticationTokenForUser(user.Email);
return Ok(new
{
authenticationToken = token.RawData,
user = new { userId = loginRequest.username }
});
}
private JwtSecurityToken GetAuthenticationTokenForUser(string userEmail)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userEmail)
};
var secretKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var audience = Identifiers.Environment.ApiUrl;
var issuer = Identifiers.Environment.ApiUrl;
var token = AppServiceLoginHandler.CreateToken(
claims,
secretKey,
audience,
issuer,
TimeSpan.FromHours(24)
);
return token;
}
}
Startup.cs
ConfigureMobileAppAuth(app, config, container);
app.UseWebApi(config);
}
private void ConfigureMobileAppAuth(IAppBuilder app, HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute("ClubrAuth", ".auth/login/ClubrAuth", new { controller = "ClubrAuth" });
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Client project:
MobileServiceUser user = await MobileClient.LoginAsync(loginProvider, jtoken);
Additionally I configured Facebook provider in azure portal like described here.
But it works only when I comment out app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions(){...}); in Startup.cs.
What I am missing to make both types of authentication works at the same time?
Since you have App Service Authentication/Authorization enabled, that will already validate the token. It assumes things about your token structure, such as having the audience and issuer be the same as your app URL (as a default).
app.UseAppServiceAuthentication() will also validate the token, as it is meant for local development. So in your example, the token will be validated twice. Aside from the potential performance impact, this is generally fine. However, that means the tokens must pass validation on both layers, and I suspect that this is not the case, hence the error.
One way to check this is to inspect the tokens themselves. Set a breakpoint in your client app and grab the token you get from LoginAsync(), which will be part of that user object. Then head to a service like http://jwt.io to see what the token contents look like. I suspect that the Facebook token will have a different aud and iss claim than the Identifiers.Environment.ApiUrl you are configuring for app.UseAppServiceAuthentication(), while the custom token probably would match it since you're using that value in your first code snippet.
If that holds true, than you should be in a state where both tokens are failing. The Facebook token would pass the hosted validation but fail on the local middleware, while the custom token would fail the hosted validation but pass the local middleware.
The simplest solution here is to remove app.UseAppServiceAuthentication() when hosting in the cloud. You will also need to make sure that your call to CreateToken() uses the cloud-based URL as the audience and issuer.
For other folks that find this issue
The documentation for custom authentication can be found here.
A general overview of App Service Authentication / Authorization can be found here.
The code you reference is only for local deployments. For Azure deployments, you need to turn on App Service Authentication / Authorization - even if you don't configure an auth provider (which you wouldn't in the case of custom auth).
Check out Chapter 2 of my book - http://aka.ms/zumobook