I'm using DotNetOpenAuth's OAuth2 library to handle authorization with another third party system. It all works great, except that the third party system is returning the UserId="testname" in the Response with the AccessToken.
I need that UserId because this third party API requires it as part of their API calls (ex: users/{userId}/account).
Using DotNetOpenAuth, I don't have access to the AccessToken response so I can't get the UserId out.
I'm calling: (_client is a WebServerClient)
var state = _client.ProcessUserAuthorization(request);
state has my AccessToken, but not the extra data sent down. Based on the DotNetOpenAuth source code the UserId came in inside the library and I don't have any access.
Is there anyway to get that UserId out using DotNetOpenAuth? Or do I need to abandon DotNetOpenAuth and try something else?
You can access request and response data by implementing IDirectWebRequestHandler and assigning it to Channel. But with current implementation of DNOA, the only way I got it to work is by applying proxy pattern to an existing UntrustedWebRequestHandlerclass, this is because this particular handler passes a CachedDirectWebResponse, which has a response stream that could be read multiple times - once by your code to retrieve additional data, and later by downstream code to ProcessUserAuthorization().
This is the code for custom IDirectWebRequestHandler :
public class RequestHandlerWithLastResponse : IDirectWebRequestHandler
{
private readonly UntrustedWebRequestHandler _webRequestHandler;
public string LastResponseContent { get; private set; }
public RequestHandlerWithLastResponse(UntrustedWebRequestHandler webRequestHandler)
{
if (webRequestHandler == null) throw new ArgumentNullException( "webRequestHandler" );
_webRequestHandler = webRequestHandler;
}
public bool CanSupport( DirectWebRequestOptions options )
{
return _webRequestHandler.CanSupport( options );
}
public Stream GetRequestStream( HttpWebRequest request )
{
return _webRequestHandler.GetRequestStream( request, DirectWebRequestOptions.None );
}
public Stream GetRequestStream( HttpWebRequest request, DirectWebRequestOptions options )
{
return _webRequestHandler.GetRequestStream( request, options );
}
public IncomingWebResponse GetResponse( HttpWebRequest request )
{
var response = _webRequestHandler.GetResponse( request, DirectWebRequestOptions.None );
//here we actually getting the response content
this.LastResponseContent = GetResponseContent( response );
return response;
}
public IncomingWebResponse GetResponse( HttpWebRequest request, DirectWebRequestOptions options )
{
return _webRequestHandler.GetResponse( request, options );
}
private string GetResponseContent(IncomingWebResponse response)
{
MemoryStream stream = new MemoryStream();
response.ResponseStream.CopyTo(stream);
stream.Position = 0;
response.ResponseStream.Position = 0;
using (var sr = new StreamReader(stream))
{
return sr.ReadToEnd();
}
}
}
And this is how we apply it and get response data:
var h = new RequestHandlerWithLastResponse(new UntrustedWebRequestHandler()); ;
_client.Channel.WebRequestHandler = h;
var auth = _client.ProcessUserAuthorization( request );
//convert response json to POCO
var extraData = JsonConvert.DeserializeObject<MyExtraData>( h.LastResponseContent );
Just read the id straight from the request, line after your call to ProcessUserAuthorization, depending on how it is passed (body, query string). I don't see any reason to stop using the DNOA.
var auth = client.ProcessUserAuthorization();
if ( auth != null )
{
// this is where you could still access the identity provider's request
...
Note that passing additional parameters together with the access token is rather uncommon and could lead to potential security issues. This is because the identity provider's response first gets to the user's browser and is then submitted to your server. The user could possibly alter the identity provider's response by keeping the access token but replacing the userid with any other valid userid.
Related
My scenario
In my scenario I have a web app that calls some API endpoints to fetch data. On the API side the user is authenticated using a JWT token. In order to improve security, the JWT token is short-lived and the API provides a refresh-token that can be used along with the expired token to request a new pair of JWT and refresh-token.
The user is authenticated on the web app side using cookie authentication. In this authentication cookie I stored both the JWT token and refresh-token (with some other claims).
The client never makes calls directly to the API. The client makes a request to the web app, that when needed calls the API using the token extracted from the authentication cookie.
If the token is expired, it refreshes it using the refresh-token.
The problem
This works fine with a single request, but the problem is when I have a page that makes multiple calls in parallel. When the token is expired, the first call is made, the token is refreshed and so the cookie and all works fine. But the others requests, that have been sent before the updated cookie is returned, fail as the refresh token cannot be used again to ask for new tokens and this results in an error.
I used ASP.NET Core 6.0 for both API and web app.
How can this scenario be managed?
What I'm currently using
The following is the helper class that I've implemented to call the API. This automatically manages expired tokens.
For brevity I posted just the method that allows to make GET calls, but the workflow is the same for POST, PATCH, PUT, DELETE .
NOTE: When a call to the API fails because of an expired token, my API sets the header Token-Expired with value true in the response (in the code I used the const string CustomResponseHeaders.ExpiredTokenHeader).
public class MyApiCaller : IApiCaller
{
private readonly HttpClient _apiClient;
private readonly IHttpContextAccessor _httpContextAccessor;
private string _token;
private string _refreshToken;
public bool IsLoggedIn => _apiClient.DefaultRequestHeaders.Authorization != null;
public Uri BaseUri { get; }
public MyApiCaller (IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
{
_apiClient = new HttpClient()
{
BaseAddress = new Uri(configuration.GetValue<string>("ApiRootUrl"))
};
_apiClient.DefaultRequestHeaders.Clear();
_apiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
BaseUri = _apiClient.BaseAddress;
_httpContextAccessor = httpContextAccessor;
}
public void SetCredentials(string token, string refreshToken)
{
_token = token;
_refreshToken = refreshToken;
_apiClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
public void Logout()
{
_token = null;
_refreshToken = null;
_apiClient.DefaultRequestHeaders.Authorization = null;
}
public async Task<HttpResponseMessage> GetAsync(Uri uri)
{
var response = await _apiClient.GetAsync(uri);
if (response.IsSuccessStatusCode)
// All is good, return the response
return response;
else
{
// Check if the request failed because of an expired token
if (response.StatusCode == HttpStatusCode.Unauthorized && response.Headers.Contains(CustomResponseHeaders.ExpiredTokenHeader))
{
// The request failed due to an expired token
// Try to refresh the token
bool refreshed = await RefreshTokenAsync();
if (!refreshed)
// Failed to refresh so return the original response
return response;
// Repeat the original request with the new token
return await GetAsync(uri);
} else
{
// It is not a token-related error
// Return the unsuccessful response
return response;
}
}
}
private async Task<bool> RefreshTokenAsync()
{
var json = JsonConvert.SerializeObject(new RefreshTokenRequest
{
Token = _token,
RefreshToken = _refreshToken
});
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync("identity/refresh-token", content);
if (response.IsSuccessStatusCode)
{
SuccessfulAuthResponse successfulAuth = await response.ToModelAsync<SuccessfulAuthResponse>(); // Custom method to desirialize a JSON response
// Set new credentials
SetCredentials(successfulAuth.Token, successfulAuth.RefreshToken);
// Update the tokens in cookie
_httpContextAccessor.HttpContext.Response.UpdateTokensInCookie(successfulAuth.Token, successfulAuth.RefreshToken);
return true;
}
else
{
return false;
}
}
}
I'm working on a multilanguage project for accademic purpose. I've written a simple Python Client that make requests to an API server written in ASP.NET. The server retrives spotify info about users. The server interacts with a DB filled by a Golang server that only makes scraping on API's exposed from Spotify. I'm aware that it's a misuse and there are better solutions
Clearly, Golang server, in order to make requests to Spotify API's, needs to know the access token returned from spotify Authorization Code Flow. Overlooking about spotify token expire time, the idea is: after user authentication through Identity module of ASP.NET server (using JWT token), associate the access token obtained calling https://accounts.spotify.com/api/token to user's informations. So, i expose an API in ASP.NET server like this
[AllowAnonymous]
[HttpPost("token")]
public async Task<ContentResult> getTokenAsync(string? code = null)
{
//to retrive information about who is the user that making call -> need later for associate spotifytoken
string accessToken = Request.Headers[HeaderNames.Authorization].ToString().Replace("Bearer ", "");
JwtSecurityTokenHandler t = new JwtSecurityTokenHandler();
var token = t.ReadJwtToken(accessToken);
var user = _userManager.FindByIdAsync(token.Subject).Result;
string s = "https://accounts.spotify.com/api/token";
if (code == null)
{
var qb = new QueryBuilder();
qb.Add("response_type", "code");
qb.Add("client_id", _config["SpotiSetting:clientId"]);
qb.Add("scope", "user-read-private user-read-email user-library-read");
qb.Add("redirect_uri", _config["SpotiSetting:redirectUser"]);
qb.Add("show_dialog", "true");
return new ContentResult
{
ContentType = "text/html",
Content = "https://accounts.spotify.com/authorize/" + qb.ToQueryString().ToString()
//Content = JsonConvert.SerializeObject(user.Result)
};
} else
{
//if i'm here, api is the callback designed for spotify
var qb = new QueryBuilder();
qb.Add("grant_type", "authorization_code");
qb.Add("code", code);
qb.Add("redirect_uri", "https://localhost:44345/spotify/token");
var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Post, s);
req.Content = new FormUrlEncodedContent(qb);
req.Headers.Authorization = new AuthenticationHeaderValue("Basic", "here_my_secret_encoded_CLIENTID:CLIENT_SECRET");
var response = await client.SendAsync(req);
var result = response.Content.ReadAsStringAsync().Result;
AccessToken json = JsonConvert.DeserializeObject<AccessToken>(result);
user.spotifyInformation.authToken = code;
user.spotifyInformation.accessToken = json;
var res = _userManager.UpdateAsync(user);
if (res.IsCompletedSuccessfully)
{
return Content("ok");
}
else
{
Content("Problem");
}
} return Content("");
}
The problem is that the second time that API is invoked, it's spotify that is sending the first authorization token (needed to request access_token), so I lost user information retrived in the first request. Should be better write two distinct API and separate callback from user request?
It's my first question here, so please to have mercy
I am in the process of rewritting our app using Xamarin.Forms with a C# backend and I'm trying to use customauth on login. I've got it working to a point but am struggling to pass back to the Xamarin app everything I want from the backend. I'm getting the token and user id but want a bit more.
The backend code on succesfull login seems relatively straightforward:
return Ok(GetLoginResult(body));
where GetLoginResult() is:
private object GetLoginResult(IUser body)
{
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, body.username)
};
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(
claims, signingKey, audience, issuer, TimeSpan.FromDays(30));
accounts account = db.accounts.Single(u => u.username.Equals(body.username));
return new LoginResult(account)
{
authenticationToken = token.RawData,
};
}
and the LoginResult class is
public class LoginResult
{
public LoginResult(accounts account)
{
Response = 200;
CustomerId = account.CustomerId;
Modules = account.Modules;
User = new LoginResultUser
{
userId = account.id,
UserName = account.UserName,
EmployeeId = account.EmployeeId
};
}
[JsonProperty(PropertyName = "Response")]
public int Response { get; set; }
etc
In the app, I'm calling the customauth as follows:
MobileServiceUser azureUser = await _client.LoginAsync("custom", JObject.FromObject(account));
The result has the token and the correct userid but how can I fill the result with the additional properties passed back by the backend? I've got the backend working and tested using postman and the results I get there are what I want but I've been unable to find out how to get it deserialized in the app.
As I known, for custom auth , MobileServiceClient.LoginAsync would invoke https://{your-app-name}.azurewebsites.net/.auth/login/custom. When using ILSPy you could find that this method would only retrieve the user.userId and authenticationToken from the response to construct the CurrentUser of your MobileServiceClient. Per my understanding, you could leverage MobileServiceClient.InvokeApiAsync to retrieve the additional user info after the user has logged in successfully. Additionally, you could try to follow this toturial for other possible approaches.
UPDATE
You could use InvokeApiAsync instead of LoginAsync to invoke the custom login endpoint directly, then retrieve the response and get the additional parameters as follows:
When logged successfully, I added a new property userName and response the client as follows:
For the client, I added a custom extension method for logging and retrieve the additional parameters as follows:
Here are the code snippet, you could refer to them:
MobileServiceLoginExtend.cs
public static class MobileServiceLoginExtend
{
public static async Task CustomLoginAsync(this MobileServiceClient client, LoginAccount account)
{
var jsonResponse = await client.InvokeApiAsync("/.auth/login/custom", JObject.FromObject(account), HttpMethod.Post, null);
//after successfully logined, construct the MobileServiceUser object with MobileServiceAuthenticationToken
client.CurrentUser = new MobileServiceUser(jsonResponse["user"]["userId"].ToString());
client.CurrentUser.MobileServiceAuthenticationToken = jsonResponse.Value<string>("authenticationToken");
//retrieve custom response parameters
string customUserName = jsonResponse["user"]["userName"].ToString();
}
}
Login processing
MobileServiceClient client = new MobileServiceClient("https://bruce-chen-002-staging.azurewebsites.net/");
var loginAccount = new LoginAccount()
{
username = "brucechen",
password = "123456"
};
await client.CustomLoginAsync(loginAccount);
I'm hoping someone can help. I have been asked to write a test application to consume a Web API.
The method I'm trying to consume, has the following signature:
[Transaction]
[HttpPost]
[Route("api2/Token/")]
public ApiToken Token(Guid companyId, DateTime dateTime, string passCode)
I've written a simple C# console application. However whatever I send to the API returns with a 404 error message and I can't see what my issue is.
My code to consume the API is as follows:
_client.BaseAddress = new Uri("http://localhost:1390");
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var companyId = Guid.Parse("4A55A43A-5D58-4245-AD7C-A72300A69865");
var apiKey = Guid.Parse("FD9AEE25-2ABC-4664-9333-B07D25ECE046");
var dateTime = DateTime.Now;
var sha256 = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(string.Format("{0}:{1:yyyyMMddHHmmss}:{2}", companyId, dateTime, apiKey));
var hash = sha256.ComputeHash(bytes);
var sb = new StringBuilder();
foreach (var b in hash)
{
sb.Append(b.ToString());
}
try
{
Console.WriteLine("Obtain an authorisation token");
var response = await _client.PostAsJsonAsync("api2/Token/", new { companyId = companyId, dateTime = dateTime, passCode = sb.ToString() });
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
All examples I've googled seem to post an object to a Web API method that accepts an object. Is it possible to post multiple simple types?
I don't think it's possible, from the documentation (https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api)
They've stated that
If the parameter is a "simple" type, Web API tries to get the value
from the URI.
For complex types, Web API tries to read the value from the message body
You can try to use uri parameters instead
var response = await _client.PostAsJsonAsync("api2/Token/{companyId}/{dateTime}/{sb.ToString()}");
Over the last few days I've been playing with the micro service pattern and all is going well but security seems to baffle me.
So If I may ask a question:
How do I handle user authentication on an individual service? At the moment I pass a request to the Gateway API which in turns connects to the service.
Question Edited Please See Below
Bearing in mind that the individual services should not know about each other. The Gateway is the aggregator as such.
Current architecture.
A little code to simulate the request:
Frontend - Client App
public class EntityRepository<T>
{
private IGateway _gateway = null;
public EntityRepository(IGateway gateway)
{
this._gateway = gateway;
}
public IEnumerable<T> FindAll()
{
return this._gateway.Get(typeof(T)).Content.ReadAsAsync<IEnumerable<T>>().Result;
}
public T FindById(int id)
{
return this._gateway.Get(typeof(T)).Content.ReadAsAsync<T>().Result;
}
public void Add(T obj)
{
this._gateway.Post(typeof(T), obj);
}
public void Update(T obj)
{
this._gateway.Post(typeof(T), obj);
}
public void Save(T obj)
{
this._gateway.Post(typeof(T), obj);
}
}
//Logic lives elsewhere
public HttpResponseMessage Get(Type type)
{
return Connect().GetAsync(Path(type)).Result;
}
public HttpResponseMessage Post(Type type, dynamic obj)
{
return Connect().PostAsync(Path(type), obj);
}
private string Path(Type type)
{
var className = type.Name;
return "api/service/" + Application.Key + "/" + className;
}
private HttpClient Connect()
{
var client = new HttpClient();
client.BaseAddress = new Uri("X");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
I use generics to determine where it needs to fire once it hit's the gateway.
So if the Type is Category it will fire the Category service thus calling:
public IEnumerable<dynamic> FindAll(string appKey, string cls)
{
var response = ConnectTo.Service(appKey, cls);
return (appKey == Application.Key) ? (response.IsSuccessStatusCode) ? response.Content.ReadAsAsync<IEnumerable<dynamic>>().Result : null : null;
}
The Gateway does not contain the physical files/Class's of the types.
After a little code, I was hoping someone could give me a little demonstration or the best approach to handle security/user authentication with the current architecture.
Case Scenario 1
User hits the web app and logs in, at that point the users encrypted email and password is sent to the Gateway API which is then passed to the User Service and decides whether the user is authenticated - all well and good but now I want to fetch all Messages from the Message Service that the user has received. I cannot really say in the Gateway if the user is authenticated, fetch the messages because that does not solve the issue of calling the Message Service outside of the Gateway API
I also cannot add authentication to each individual service because that would require all respective services talking to the User Service and that defeats the purpose of the pattern.
Fixes:
Only allow the Gateway to call the Services. Requests to services outside of the Gateway should be blocked.
I know security is a broad topic but within the current context, I'm hoping someone could direct me with the best course of action to resolve the issue.
Currently I have Hardcoded a Guid in all off the applications, which in turn fetches data if the app is equal.
Edit
This answer is about the Gateway <-> Micro service communication. The user should of course be properly authenticated when the App talks with the gateway
end edit
First of all, the micro services should not be reachable from internet. They should only be accessible from the gateway (which can be clustered).
Second, you do need to be able to identify the current user. You can do it by passing the UserId as a HTTP header. Create a WebApi filter which takes that header and creates a custom IPrincipal from it.
Finally you need some way to make sure that the request comes from the gateway or another micro service. An easy way to do that is to use HMAC authentication on a token.
Store the key in the web.config for each service and the gateway. Then just send a token with each request (which you can authenticate using a WebApi authentication filter)
To generate a hash, use the HMACSHA256 class in .NET:
private static string CreateToken(string message, string secret)
{
secret = secret ?? "";
var keyByte = Encoding.ASCII.GetBytes(secret);
var messageBytes = Encoding.ASCII.GetBytes(message);
using (var hasher = new HMACSHA256(keyByte))
{
var hashmessage = hasher.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
So in your MicroServiceClient you would do something like this:
var hash = CreateToken(userId.ToString(), mySharedSecret);
var myHttpRequest = HttpRequest.Create("yourUrl");
myHttpRequest.AddHeader("UserId", userId);
myHttpRequest.AddHeader("UserIdToken", hash);
//send request..
And in the micro service you create a filter like:
public class TokenAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
{
protected string SharedSecret
{
get { return ConfigurationManager.AppSettings["SharedSecret"]; }
}
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
await Task.Run(() =>
{
var userId = context.Request.Headers.GetValues("UserId").FirstOrDefault();
if (userId == null)
{
context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
return;
}
var userIdToken = context.Request.Headers.GetValues("UserIdToken").FirstOrDefault();
if (userIdToken == null)
{
context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
return;
}
var token = CreateToken(userId, SharedSecret);
if (token != userIdToken)
{
context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
return;
}
var principal = new GenericPrincipal(new GenericIdentity(userId, "CustomIdentification"),
new[] {"ServiceRole"});
context.Principal = principal;
});
}
public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
}
public bool AllowMultiple
{
get { return false; }
}
private static string CreateToken(string message, string secret)
{
secret = secret ?? "";
var keyByte = Encoding.ASCII.GetBytes(secret);
var messageBytes = Encoding.ASCII.GetBytes(message);
using (var hasher = new HMACSHA256(keyByte))
{
var hashmessage = hasher.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
}
Option 1 (Preferred)
The easy way is the micro services should be behind the gateway, hence you would whitelist services to connect to them, meaning only authorized and trusted parties have access (i.e. the gateway only). Clients shouldn't have direct access to them. The Gateway is your night club bouncer.
Option 2
You can use a JWT or some form of token and share the secret key between the services. I use JWT Authorization Bearer tokens.
The other services don't need to query the user service, they just need to know that the token is valid, then they have authorization to use the API. I get the JWT passed from the client to the gateway and inject it into the request that is sent to the other service behind, just a straight pass through.
The micro service behind needs to have the same JWT consumption as the gateway for authorization but as I mentioned that is just determining a valid token, not querying a valid user.
But this has an issue that once someone is authorized they can jump call upon other users data unless you include something like a claim in the token.
My Thoughts
The part that I found a challenge from Monolithic to Micro Services was that you needed to switch where you place your trust. In Monolithic you control everything you are in charge. The point of Micro Services is that other services are in complete control of their domain. You have to place your trust in that other service to fulfill its obligations and not want to recheck and reauthorize everything at every level beyond what is necessary.