Im implementing Aspnet OpenIdConnect Server (ASOS) in a asp.net core 1.1 project and im currently trying to implement some integration testing (xunit & moq) using Microsoft.AspNetCore.TestHost.TestServer.
The issue i have is manually generating a fake accesstoken with which to populate a AuthenticationHeaderValue for the HttpClient requests. Searched for a working solution to this but sofar ive been unsuccessful.
So my question: Anyone has a tip as how to manually generating accesstokens for TestServer without having to call the token endpoint of ASOS for testing?
While ASOS deliberately prevents you from creating tokens from arbitrary places (they can be only generated during OpenID Connect requests), you can directly use the underlying ASP.NET Core APIs to generate fake tokens that will be accepted by the OAuth2 validation middleware:
var provider = container.GetRequiredService<IDataProtectionProvider>();
var protector = provider.CreateProtector(
nameof(OpenIdConnectServerMiddleware),
OpenIdConnectServerDefaults.AuthenticationScheme, "Access_Token", "v1");
var format = new TicketDataFormat(protector);
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.Name, "Bob le Bricoleur"));
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
var token = format.Protect(ticket);
That said, it's rarely the most efficient method if you want to test your own token-protected APIs. Instead, I'd recommend setting HttpContext.User or using the OAuth2 validation middleware events to use fake identities without involving crypto operations.
You can also mock the AccessTokenFormat:
[Fact]
public async Task ValidTokenAllowsSuccessfulAuthentication()
{
// Arrange
var server = CreateResourceServer();
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token");
// Act
var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Fabrikam", await response.Content.ReadAsStringAsync());
}
private static TestServer CreateResourceServer(Action<OAuthValidationOptions> configuration = null)
{
var builder = new WebHostBuilder();
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>(MockBehavior.Strict);
format.Setup(mock => mock.Unprotect(It.Is<string>(token => token == "invalid-token")))
.Returns(value: null);
format.Setup(mock => mock.Unprotect(It.Is<string>(token => token == "valid-token")))
.Returns(delegate
{
var identity = new ClaimsIdentity(OAuthValidationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "Fabrikam"));
var properties = new AuthenticationProperties();
return new AuthenticationTicket(new ClaimsPrincipal(identity),
properties, OAuthValidationDefaults.AuthenticationScheme);
});
builder.ConfigureServices(services =>
{
services.AddAuthentication();
});
builder.Configure(app =>
{
app.UseOAuthValidation(options =>
{
options.AccessTokenFormat = format.Object;
// Run the configuration delegate
// registered by the unit tests.
configuration?.Invoke(options);
});
// Add the middleware you want to test here.
app.Run(context =>
{
if (!context.User.Identities.Any(identity => identity.IsAuthenticated))
{
return context.Authentication.ChallengeAsync();
}
var identifier = context.User.FindFirst(ClaimTypes.NameIdentifier).Value;
return context.Response.WriteAsync(identifier);
});
});
return new TestServer(builder);
}
Related
I am trying to authenticate an Appwrite user with my ASP.NET Core 7 Web API. In the past, I used Firebase for this with which I was able to implement the function as following:
private static void ConfigureFirebaseAuthentication(IServiceCollection services,
IConfiguration configuration)
{
var options = new AppOptions() { Credential = GoogleCredential.FromFile("firebase-config.json") };
FirebaseApp.Create(options);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(
opt =>
{
opt.IncludeErrorDetails = true;
opt.Authority = configuration["FirebaseAuthentication:ValidIssuer"];
opt.TokenValidationParameters = new()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["FirebaseAuthentication:ValidIssuer"],
ValidAudience = configuration["FirebaseAuthentication:ValidAudience"]
};
}
);
}
This validated the request against the firebase API, but I don't see how I am able to implement something similar for Appwrite. Also the docs don't mention anything helpful.
Does anyone know how to achieve this?
Unfortunately, Appwrite doesn't have a .NET SDK yet so you would have to manually make the API call. I don't know .NET very well, but I generated code using the API specs and Insomnia:
var client = new HttpClient();
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("https://[HOSTNAME]/v1/account/sessions/email"),
Headers =
{
{ "X-Appwrite-Project", "[PROJECT ID]" },
},
Content = new StringContent("{\n \"email\": \"[EMAIL]\",\n \"password\": \"[PASSWORD]\"\n}")
{
Headers =
{
ContentType = new MediaTypeHeaderValue("application/json")
}
}
};
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
If this is successful, you can grab the X-Fallback-Cookies response header and use that for future requests.
Otherwise, if you don't want to create a session server side and you have an Appwrite JWT token generated from your front end, you can make API calls to Appwrite and pass the JWT token in the X-Appwrite-JWT header to make requests on behalf of the user.
For more information on working directly with the Appwrite REST API, refer to the REST docs.
I'm currently trying to implement Integration Testing in an environment with 2 servers:
A .NET Core API server
An IdentityServer4 Authentication Server
Through much struggle, I've managed to get the two to communicate with eachother, however, IdentityServer throws the following error when trying to confirm the JWT token (Through the API):
Bearer was not authenticated. Failure message: IDX10501: Signature
validation failed. Unable to match key: kid:
'8C1D5950D083E20D4B20DE9B37AC71FAEF679469'.
I'll try to keep the code sample brief:
In the XUnit startup, I configure and create clients for both TestServers.
public APITestBase(ITestOutputHelper output)
{
_output = output;
var apicon = new ConfigurationBuilder()
.AddJsonFile("apisettings.json", optional: true, reloadOnChange: true)
.Build();
var authcon = new ConfigurationBuilder()
.AddJsonFile("authsettings.json", optional: true, reloadOnChange: true)
.Build();
_authServer = new TestServer(new WebHostBuilder()
.UseConfiguration(authcon)
.ConfigureLogging(x => x.AddXUnit(_output))
.UseEnvironment("Development")
.UseStartup<Auth.Startup>());
_authClient = _authServer.CreateClient();
_server = new TestServer(new WebHostBuilder()
.UseConfiguration(apicon)
.ConfigureLogging(x => x.AddXUnit(_output))
.UseEnvironment("Test")
.ConfigureServices(
services => {
//Here, I'm adding an httpclienthandler. In the application I will use this as JWTBackChannelHandler. This allows communication between the two servers
services.AddSingleton<HttpMessageHandler>(_authServer.CreateHandler());
})
.UseStartup<Api.Startup>());
_client = _server.CreateClient();
APIConfig = new Cnfg();
apicon.Bind("APIConfig", APIConfig);
//Make users in api
}
Here is the actual test I am trying to run
[Fact]
public async Task ApplyTest()
{
_client.SetBearerToken(await GetAccessToken());
// Act
var response = await _client.GetAsync($"{APIConfig.ApiBaseUrl}api/call/");
response.EnsureSuccessStatusCode();
}
And here is GetAccessToken, which returns the JWT token that the integration uses to identify itself as a user... At least that's the plan.
protected async Task<string> GetAccessToken()
{
var disco = await _authClient.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest()
{
Address = _authServer.BaseAddress.ToString(),
Policy =
{
ValidateIssuerName = false
}
});
if (!String.IsNullOrEmpty(disco.Error))
{
throw new Exception(disco.Error);
}
var response = await _authClient.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
GrantType = "password",
ClientId = "api",
UserName = "test#test.nl",
Password = "test"
});
return response.AccessToken;
}
The only identifiable difference I can find between a JWT token generated through this function and that of a normal website request is that the Public Key is missing (according to jwt.io).
Finally, because I think this bit might be useful, here's the part of the API's Startup.cs where I enforce the use of the JWTBackChannelHandler
if (Environment.EnvironmentName == "Test")
{
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, o =>
{
o.JwtBackChannelHandler = _identityServerMessageHandler;
o.RequireHttpsMetadata = false;
})
.AddApiKeyAuthenication(o =>
{
o.Keys.Add("****".ToSha256());
o.Keys.Add("****".ToSha256());
});
}
Thank you very much in advance for your help, I've kind of reached the end of my wits with this one. Please let me know if you want any more information and I'll be happy to provide.
After another 2 hours of digging I found out that I needed to add
o.Authority = identityServerOptions.Address.AbsoluteUri;
To the AddIdentityServerAuthentication.
Thank you to those who took the time into digging into my issue for me.
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;
});
}
}
Hopefully I'm not misunderstanding something fundamental about ASP.NET Authentication, but here's what I'm trying to accomplish (and failing doing such):
I have a Xamarin.Forms app that needs to be able to go after ASP.NET web services. When I run the App itself, it works fine. The ASP.NET side of it uses Cookie Authentication and the App is capable of getting the Cookie from the Cookie Container -- and then serializing/storing it into secure storage:
For example:
var baseUri = new Uri(url);
baseUri = new Uri($"{baseUri.Scheme}://{baseUri.Host}");
var cookieContainer = new CookieContainer();
/*
authenticationCookie is stored locally and de/serialized via Json from SecureStorage:
var cookie = SecureStorage.GetAsync(AuthenticationCookieKey).Result;
if (!string.IsNullOrEmpty(cookie))
{
authenticationCookie = JsonConvert.DeserializeObject<Cookie>(cookie);
}
*/
cookieContainer.Add(authenticationCookie);
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
{
using (var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = new FormUrlEncodedContent(content) })
{
using (var client = new HttpClient(handler))
{
return client.SendAsync(request).Result;
}
}
}
When I make a Login, I do the following on "success":
SetAuthenticationCookie(cookieContainer.GetCookies(baseUri)[0]);
This sets the local "authenticationCookie" in the class and then serializes it out to SecureStorage.
I've proven/checked that the authenticationCookie is correctly de/serializing and loads up when the Xamarin.Forms app does. I've attached it to my web request. However, when I make the calls, I get a Login request from the opposite end on ASP.NET Core.
The ASP.NET Core server itself works fine normally. If I have a browser instance, it never asks me to login and the browser "remembers" and applies the cookie from login correctly. However, it should seem that the Xamarin.Forms app does not.
What am I missing?
For the sake of argument, this is how I am setting up cookies via the Login method:
//user has a User loaded from EF at this point
var userPrincipal = new ClaimsPrincipal(
new ClaimsIdentity(
new List<Claim>()
{
new Claim(ClaimTypes.Name, user.EmailAddress)
}, "Login"));
var properties = new AuthenticationProperties()
{
IsPersistent = true
};
await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userPrincipal, properties);
So, I was successful in using the answer UNDER the selected answer:
How do I set a cookie on HttpClient's HttpRequestMessage
Apparently setting the CookieContainer and then populating it with a Cookie and including that in the Message DID NOT WORK.
The solution was to manually add the Cookie doing the following:
private HttpResponseMessage GetDataResponse(string url)
{
using (var handler = new HttpClientHandler { UseCookies = false })
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
request.Headers.Add("Cookie", $"{MyCookie.Name}={MyCookie.Value}");
using (var client = new HttpClient(handler))
{
return client.SendAsync(request).Result;
}
}
}
}
This works exactly as we would expect!
I am using web api for accessing data and I want to authenticate and authorize web api.For that I am using JWT token authentication. But I have no idea where should I store access tokens?
What I want to do?
1)After login store the token
2)if user want to access any method of web api, check the token is valid for this user,if valid then give access.
I know two ways
1)using cookies
2)sql server database
which one is the better way to store tokens from above?
Alternatively, if you just wanted to authenticate using JWT the implementation would be slightly different
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var user = context.Principal.Identity.Name;
//Grab the http context user and validate the things you need to
//if you are not satisfied with the validation fail the request using the below commented code
//context.Fail("Unauthorized");
//otherwise succeed the request
return Task.CompletedTask;
}
};
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("MyVeryStrongKeyHiddenFromAnyone"),
ValidateIssuer = false,
ValidateAudience = false
};
});
still applying use authentication before use MVC.
[Please note these are very simplified examples and you may need to tighten your security more and implement best practices such as using strong keys, loading configs perhaps from the environment etc]
Then the actual authentication action, say perhaps in AuthenticationController would be something like
[Route("api/[controller]")]
[Authorize]
public class AuthenticationController : Controller
{
[HttpPost("authenticate")]
[AllowAnonymous]
public async Task<IActionResult> AuthenticateAsync([FromBody]LoginRequest loginRequest)
{
//LoginRequest may have any number of fields expected .i.e. username and password
//validate user credentials and if they fail return
//return Unauthorized();
var claimsIdentity = new ClaimsIdentity(new Claim[]
{
//add relevant user claims if any
}, "Cookies");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
return Ok();
}
}
in this instance I'm using cookies so I'm returning an HTTP result with Set Cookie. If I was using JWT, I'd return something like
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]LoginRequest loginRequest)
{
//validate user credentials and if they validation failed return a similar response to below
//return NotFound();
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("MySecurelyInjectedAsymKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
//add my users claims etc
}),
Expires = DateTime.UtcNow.AddDays(1),//configure your token lifespan and needed
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey("MyVerySecureSecreteKey"), SecurityAlgorithms.HmacSha256Signature),
Issuer = "YourOrganizationOrUniqueKey",
IssuedAt = DateTime.UtcNow
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(4);//you can set this to a suitable timeframe for your situation
cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", tokenString, cookieOptions);
return Ok();
}
I'm not familiar with storing your users tokens on your back end app, I'll quickly check how does that work however if you are using dotnet core to authenticate with either cookies or with jwt, from my understanding and experience you need not store anything on your side.
If you are using cookies then you just need to to configure middleware to validate the validity of a cookie if it comes present in the users / consumer's headers and if not available or has expired or can't resolve it, you simply reject the request and the user won't even hit any of your protected Controllers and actions. Here's a very simplified approach with cookies.(I'm still in Development with it and haven't tested in production but it works perfectly fine locally for now using JS client and Postman)
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "yourCookieName";
options.Cookie.SameSite = SameSiteMode.None;//its recommended but you can set it to any of the other 3 depending on your reqirements
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
{
OnRedirectToLogin = redirectContext =>//this will be called if an unauthorized connection comes and you can do something similar to this or more
{
redirectContext.HttpContext.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnValidatePrincipal = context => //if a call comes with a valid cookie, you can use this to do validations. in there you have access to the request and http context so you should have enough to work with
{
var userPrincipal = context.Principal;//I'm not doing anything with this right now but I could for instance validate if the user has the right privileges like claims etc
return Task.CompletedTask;
}
};
});
Obviously this would be placed or called in the ConfigureServices method of your startup to register authentication
and then in your Configure method of your Startup, you'd hookup Authentication like
app.UseAuthentication();
before
app.UseMvc()