IdentityServer4: How to include email in access_token without the client explicitly requesting it? - oauth

when the user logs in on the IdentityServer4 via Google, I'd like to access the email (and maybe their google-id) but without having the client request it. So it should be accessible every time, so I can put it in the access_token (because our API needs the user's email address).
I've been injecting into the IProfileService, also the IClaimsService but I can't find the email there. Would it be possible to hook into the Google-SignIn Callback so I can access the response manually?
Thanks!

I solved it by adding the claims I needed in AccountController.ExternalLoginCallback like this:
//Add E-Mail claim even if client didn't ask for it
if (claims.Exists(c => c.Type.Equals(ClaimTypes.Email))) {
additionalClaims.Add(new Claim(JwtClaimTypes.Email, claims.FirstOrDefault(x => x.Type.Equals(ClaimTypes.Email)).Value));
}
then I added the claim to the access_token by dependency injecting my ProfileService class and adding the claims in the MyProfileService.GetProfileDataAsync like this:
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var claims = new List<Claim>();
Claim emailClaim = context.Subject.Claims.Where<Claim>(claim => claim.Type.Equals(JwtClaimTypes.Email)).FirstOrDefault();
if (emailClaim != null)
{
claims.Add(emailClaim);
}
context.IssuedClaims = claims;
return Task.FromResult(0);
}

Related

Initiate and store multiple OAuth2 external authentication challenges in a ASP.NET Core MVC application?

I can authenticate against two separate OAuth authentication schemes but it seems only one can be active at a time. I'd like to compare data from two separate SaaS applications and therefore I need two separate Bearer tokens. How can I initiate multiple OAuth challenges when the user loads the application and then store the Bearer Tokens for each? (e.g. in the Context.User cookie?)
My Startup.cs is as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/signin";
options.LogoutPath = "/signout";
})
.AddScheme1 (options =>
{
options.ClientId = Configuration["Scheme1:ClientId"];
options.ClientSecret = Configuration["Scheme1:ClientSecret"];
options.Scope.Add("scope1");
options.SaveTokens = true;
})
.AddScheme2(options =>
{
options.ClientId = Configuration["Scheme2:ClientId"];
options.ClientSecret = Configuration["Scheme2:ClientSecret"];
options.Scope.Add("scope1");
options.SaveTokens = true;
});...
}
The AuthenticationController calls the Challenge overloaded method from the Microsoft.AspNetCore.Mvc.Core assembly that takes a single provider/scheme (passing multiple schemes in the overloaded method seems to be ignored).
[HttpGet("~/signin")]
public async Task<IActionResult> SignIn() => View("SignIn", await HttpContext.GetExternalProvidersAsync());
[HttpPost("~/signin")]
public async Task<IActionResult> SignIn([FromForm] string provider)
{
...
return Challenge(new AuthenticationProperties { RedirectUri = "/" }, provider);
}
Presumably, you'd prompt the user to sign-into one external application, redirect back to the home page, and then prompt them to sign-into the second one, and then allow them to start using the application proper.
If this is possible - e.g. using a "multiple" Auth cookie - how then would I fetch the correct Bearer token and User values for the given scheme? Currently you just seem to fetch the token with a generic "access_token" name and unique user values:
string accessToken = await HttpContext.GetTokenAsync("access_token");
string userID = User.FindFirstValue(ClaimTypes.NameIdentifier);
There does seem to be some information here regarding using a SignInManager but I'm unable to determine if this is applicable to this problem.
I would aim to start with a standard architecture where the user authenticates with the one and only app, and gets only one set of tokens, issued by your own Authorization Server.
SaaS DATA - OPTION 1
Does the user need to get involved in these connections or can you use a back end to back end flow here?
Your C# code could connect to the SaaS provider with the client credentials grant, using the client ID and secret that you reference above. Provider tokens would then be cached in memory, then used by the back end code to return provider data to the UI. This is a simple option to code.
SaaS DATA - OPTION 2
If the user needs to get involved, because the data is owned by them, you might offer UI options like this. After each click the user is redirected again, to get a token for that provider.
View provider 1 data
View provider 2 data
Aim to emulate the embedded token pattern, where the provider tokens are available as a secondary credential. How you represent this could vary, eg you might prefer to store provider tokens in an encrypted cookie.
CODING AND SIMPLICITY
I would not mix up provider tokens with the primary OAuth mechanism of signing into the app and getting tokens via the .NET security framework, which typically implements OpenID Connect. Instead I would aim to code the SaaS connections on demand.
I think you will find it easier to code the SaaS connections with a library approach, such as Identity Model. This will also help you to deal with SaaS provider differences more easily.
I assume you use OIDC schemes.
First, you need to add two cookie schemes, one for each OIDC authentication scheme as their sign in scheme and set their callback path to different values to stop them competing:
services.AddAuthentication()
.AddCookie("Cookie1")
.AddCookie("Cookie2")
.AddOpenIdConnect("OidcScheme1", opt =>
{
opt.SignInScheme = "Cookie1";
opt.CallbackPath = "/signin-oidc-scheme1";
opt.SaveTokens = true;
})
.AddOpenIdConnect("OidcScheme2", opt =>
{
opt.SignInScheme = "Cookie2";
opt.CallbackPath = "/signin-oidc-scheme2";
opt.SaveTokens = true;
});
This will instruct the OIDC handler to authenticate the user from corresponding cookie.
Second, you need a controller action to challenge the user against each OIDC scheme:
[HttpGet]
[Route("login")]
[AllowAnonymous]
public IActionResult Login([FromQuery]string scheme,
[FromQuery]string? returnUrl)
{
return Challenge(new AuthenticationProperties
{
RedirectUri = returnUrl ?? "/"
}, scheme);
}
From your web app, you need to send the user to the Login endpoint twice with different scheme values:
GET /login?scheme=OidcScheme1
GET /login?scheme=OidcScheme2
Or chain them together using the returnUrl:
GET /login?scheme=OidcScheme1&returnUrl=%2Flogin%3Fscheme%3DOidcScheme2
Once signed in, there should be two cookies in the browser window, for example:
To authenticate the user and restore both identities from two cookies, you can use authorization policy:
[HttpGet]
[Authorize(AuthenticationSchemes = "OidcScheme1,OidcScheme2")]
public async Task<IActionResult> SomeOperation()
{
// Two identities, one from each cookie
var userIdentities = User.Identities;
...
}
To get access token from each authentication scheme, use the method you discovered (GetTokenAsync) and specify authentication scheme:
var token1 = await HttpContext.GetTokenAsync("OidcScheme1", "access_token");
var token2 = await HttpContext.GetTokenAsync("OidcScheme2", "access_token");
It is possible that the access token is not returned from the token endpoint depends on the response_type you used. If this is the case, try set the OpenIdConnectionOptions.ResponseType to OpenIdConnectResponseType.Code and make sure the scope is correct.
I encountered a similar problem where we had microservices that are/were shared across multiple products with each product having a separate IDP tenant (essentially a different token issuer). Perhaps a similar approach might work for your scenario...
The following link helped me with a solution - see here.
Basically I defined a smart authentication scheme
var builder = services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = "smart";
//...
});
Then in the smart policy scheme definition, I decode the JWT coming in to work out the issuer from the iss claim in the JWT, so that I can forward to the correct location for JWT bearer authentication.
builder.AddPolicyScheme("smart", "smart", options =>
{
options.ForwardDefaultSelector = context =>
{
var jwtEncodedString = context.Request.Headers["Authorization"].FirstOrDefault()?.Substring(7);
if (string.IsNullOrEmpty(jwtEncodedString))
return settings.Tenants.First().Key; // There's no authorization header, so just return any.
var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);
var issuer = token.Claims.First(c => c.Type == "iss").Value?.TrimEnd('/');
var tenant = settings.Tenants
.Where(pair => pair.Value.Issuer.TrimEnd('/') == issuer)
.Select(pair => pair.Key).FirstOrDefault();
if (tenant == null)
throw new AuthorizationException($"Failed to locate authorization tenant with issuer '{issuer}'.");
return tenant;
};
});
Note: settings.Tenants is just an array of whitelisted tenants (from appsettings) that I configure as follows:
foreach (var tenant in settings.Tenants)
builder.AddJwtBearer(tenant.Key, options => Configure(options, tenant.Value, defaultJwtBearerEvents));

.Net Core API Google authentication JWT create or reuse google token?

Hi I want to have users authenticate using Google and I want my API and also use their Google token to communicate on their behalf with google.
Here is a diagram so far. The ???? is where I'm wondering what should I return the the client.
a) Should I return my own JWT and use it to authenticate all other client request? But then to communicate with google on their behalf I have to store their token which I dont want to
b) Should I return the google token to the client for them to authenticate their requests with it? Do I have a out-of-the-box middleware for authenticating their tokens again with google? Or should I write one myself?
c) Some other option?
Basically I need their google token so I can talk with google API but I dont want to store it on my end and also I dont want the client to need to send my JWT and their google token with each request.
EDIT
This is my custom google token validator but this is just the validation of the google token when the client sends it with a request.
public class CustomGoogleTokenValidator : ISecurityTokenValidator
{
private readonly JwtSecurityTokenHandler tokenHandler;
private readonly ILogger logger;
public bool CanValidateToken => true;
public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;
public CustomGoogleTokenValidator(ILogger logger)
{
tokenHandler = new JwtSecurityTokenHandler();
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public bool CanReadToken(string securityToken)
{
return tokenHandler.CanReadToken(securityToken);
}
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
validatedToken = null;
var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings()).Result;
// TODO VALIDATE
//payload.Audience == "myclientid";
//payload.Issuer == "accounts.google.com" or "https://accounts.google.com"
//payload.ExpirationTimeSeconds > 0;
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, payload.Name),
new Claim(ClaimTypes.Name, payload.Name),
new Claim(JwtRegisteredClaimNames.FamilyName, payload.FamilyName),
new Claim(JwtRegisteredClaimNames.GivenName, payload.GivenName),
new Claim(JwtRegisteredClaimNames.Email, payload.Email),
new Claim(JwtRegisteredClaimNames.Sub, payload.Subject),
new Claim(JwtRegisteredClaimNames.Iss, payload.Issuer),
};
try
{
var principle = new ClaimsPrincipal();
principle.AddIdentity(new ClaimsIdentity(claims));
return principle;
}
catch (Exception e)
{
this.logger.Error(e, "Error while creating claims priciple.");
throw;
}
}
}
I still don't know if it's appropriate and enought to just send google token to them after I validate it on login. Like below or should I create a new jwt with claims or somethig else?
[AllowAnonymous]
[HttpPost("google")]
public async Task<IActionResult> Google([FromBody]GoogleLoginDto loginDto)
{
try
{
var payload = await GoogleJsonWebSignature.ValidateAsync(loginDto.TokenId, new GoogleJsonWebSignature.ValidationSettings());
// TODO Check if user exists if not create new one...
var user = this.GetUsers().FirstOrDefault(u => u.Email == payload.Email);
return Ok(new
{
token = loginDto.TokenId
});
}
catch (Exception ex)
{
BadRequest(ex.Message);
}
return BadRequest();
}
In oauth, there are server roles like client, resource owner, authorization server, resource server. The resource should be protected and grant authorization like figure below:
However, as far as I know, Google doesn't support protecting the customer's resource like web API. You can refer to the scenarios covered from below(OAuth 2.0 Overview). The most scenarios are about how to implement OAuth 2.0 authorization to access Google APIs(resource). It seems that your scenario more likes on-behalf-flow. You may check whether OAuth 2.0 for the service account to see if it fits your scenario.
And for technically, if you trust the Google's authorization server, you can verify the token as the code in your post. However in this scenario, you should verify the signature(JWT token) first ensure that the token was issued from Google, before you verify the claims. Here is an thread about verify AAD token, I answered before you can refer.
To understand concepts about OAuth 2.0 Authorization Framework, you can refer to rfc6749. And for the individually identity platform support OAuth, you need to check it on each platform(Microsoft, Google, etc.).

allow mvc5 c# webapi so that only my app can access it

I am making the api calls through the controller action method as below. The following is the working code of it.
But I want to secure the webapi so that only my application can access it. I have seen sources with login credentials
but in my case it is a public facing website with no login users.
Only the calls from my application should access it. Could anyone please suggest what can be done. or Is my current code with ValidateReferrer is suffice to handle?
[HttpGet]
[ValidateReferrer]
[ActionName("GetFind")]
[CacheOutput(ClientTimeSpan = 300, ServerTimeSpan = 300)]
public ApiQueryResponse GetFind(string query)
{
return _Worker.GetFind(query);
}
Validate Referrer in the controller has the following implementation:
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.HttpContext == null)
{
throw new System.Web.HttpException("No Http context, request not allowed.");
}
else
{
if (filterContext.HttpContext.Request.UrlReferrer == null)
{
throw new System.Web.HttpException("Referrer information missing, request not allowed.");
}
else if (filterContext.HttpContext.Request.UrlReferrer.Host != filterContext.HttpContext.Request.Url.Host)
{
throw new System.Web.HttpException(string.Format("Possible cross site request forgery attack, request sent from another site: {0}", filterContext.HttpContext.Request.UrlReferrer.Host));
}
}
}
In the worker class,
public ApiQueryResponse GetFind(string query)
{
var results = GetResults(Settings.ApiKey, SetFindParameters(query), Resource);
return results;
}
private ApiQueryResponse GetResults(string apiKey, string parameterQuery, string Resource)
{
var results = new ApiQueryResponse();
if (apiKey != null && !String.IsNullOrWhiteSpace(apiKey))
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetAsync(string.Format("{0}/{1}?{2}&key={3}", WebApiUrl, Resource, parameterQuery, apiKey)).Result;
if (response.IsSuccessStatusCode)
{
var responseBodyAsText = response.Content.ReadAsStringAsync().Result;
results = JsonConvert.DeserializeObject<ApiQueryResponse>(responseBodyAsText);
}
}
}
return results;
}
Again this is the case where you have to authenticate your "application" but not users. If you check facebook/twitter/gmail api's, they have a client secret and client id to authenticate the application. But still there will be an "Authorize" call made with this id and secret for which the api returns a token and this token is used henceforth to authorize the other requests. This token will also have an expiry and there are methods to get refresh tokens.
Thus said, you have to take a call on how much security you have to implement for your api's. You could have a similar approach where your client first asks for a security token by providing the client id and secret (which should really be a secret). You can check this id and secret against your store (may be database) and if that passes the validation, you can send back a token which you could authroize using [Authroize] attribute or by custom validation.
How to create tokens should be another discussion IMO. Simple approach is mentioned here for eg- how to generate a unique token which expires after 24 hours?. There are other standard ways of generating tokens JWT/OAuth tokens.
EDIT
As a simple approach (not taking much security aspects into consideration) would be:
Create an app secret (may be a Guid value)
While sending request, take current timestamp and encrypt (have your
own encrypt and decrypt logic) the timestamp with the app secret. Lets call that encrypted value as 'token'
Pass the token in your request header (may be a custom header,say,
x-my-auth)
In the api, have a custom authorize filter
In the custom filter, overridden OnAuthroizeCore method, get the
token from request header
Decrypt the token using the same app secret and you will get the
timestamp sent from the client
If decryption is fine, then we are through the first step/ or the
token passed the first step
Additionaly, check whether the difference between the current time
and the time decrypted from token is more than 5(*you can have your
own expiry value)
If the difference is more than your expiry limit, return false which
would throw unauthorized exception back to the client (do the same if the token fails to decrypt)
The expiry check is to handle the scenario where someone hacking your
token from the request and then using it afterwards. In case if he
uses the token after your expiry, this would throw unauthorized
Consider the above logic and entire description just as a "food for thought" and DO NOT use it without proper research and understanding. My idea was to give some basic idea about the application authentication until someone really good at this writes up a really nice article in this post

Save claims to db on login

I have an MVC5 application that is using Oauth and is logging in correctly however I want to save the returned email to the database and am having trouble doing this.
I have the following code in startup.auth.cs:
The below code adds the claim fine and I can the email claim in the debugger, however if it is the users first login then the user isn't actually created at this point so I can't update the user here.
LinkedInAuthenticationOptions x = new LinkedInAuthenticationOptions();
x.ClientId = "key";
x.ClientSecret = "key";
x.Scope.Add("r_emailaddress");
x.Provider = new LinkedInAuthenticationProvider()
{
OnAuthenticated = async context =>
{
//Get the access token
context.Identity.AddClaim(
new System.Security.Claims.Claim("Email", context.Email));
}
};
app.UseLinkedInAuthentication(x);
In the below code (in the default project accountcontroller.cs) I try to access the email claim that I added above however it is unable to find the claim...
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
ClaimsIdentity identity = UserManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
}
Does anybody know how I can access the email claim that I added in startup.auth.cs ?
You want to do something similar to this blog post, just store the email claim instead of the Facebook access token.
How to get more data from external providers?

Sending additional data along with Auth DTO in ServiceStack

I have an API for web services, Android apps, etc., using ServiceStack. I currently authenticate with a username/password combo that looks something like this on the client side:
var authResponse = authService.Authenticate(new Auth {
provider = "credentials",
UserName = user.user_id,
Password = user.password,
RememberMe = true
});
I'd like to do something like:
var authResponse = authService.Authenticate(new Auth {
provider = "credentials",
UserName = user.user_id,
Password = user.password,
ClientCode = user.clientCode <=====
RememberMe = true
});
At this point I need to send a client code along with the username/password. On the other end I will be using that client code to determine which database I should authenticate against.
Since it doesn't seem to be possible to extend the Auth class at this point in time, I'm wondering if there is a more simple place to start.
Thanks
I would recommend to use different authentication provider implementation for this. Here are more details:
Create a separate class for each of your client code like:
public class Code1AuthProvider : CredentialsAuthProvider
{
public Code1AuthProvider ()
{
this.Provider = "Code1";
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary authInfo)
{
}
}
Same way create separate classes for each code.
Now you need to register all your providers in your project's AppHost file.
Now you can access /Auth/Code1 as your authentication. Since you are using separate databases, it will be easier to manage code this way.
use [Authenticate("Code1")] where you want the service to be authenticated for a specific type of user.
At client side you can use appropriate /Auth/ClientCode url to authenticate.
You could try passing a header value for the client code. You can search for examples that pass API keys.

Resources