Login page customized depending on client - identityserver4

I would like to make the login page know which client requested the login in order to display some client-specific branding: Otherwise the user may be confused as to why he's redirected to this foreign login page on a different domain. A client logo will help reassure him that he's still on the right track.
What would be the most reasonable approach to get at that information?
EDIT: Note that by "client" I'm referring to the client web applications on whose behalf the authentication happens - not the user's browser. All clients are under my control and so I'm using only the implicit workflow.
To make this even more clear: I have client web apps A and B, plus the identity server I. When the user comes to I on behalf of B, the B logo should appear as we're no longer on B's domain and that may be confusing without at least showing a B-related branding.

Some Theory
The easiest way to get the ClientId from IdSrv 4 is through a service called IIdentityServerInteractionService which is used in the Account Controller to get the AuthorizationContext. And then follow that up with the IClientStore service that allows you to get the client details given the ClientId. After you get these details then its only a matter of sending that info to the view for layout. The client model in IdSrv 4 has a LogoUri property that you can utilize to show an image at login per client.
Simple Example
// GET: /Account/Login
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Login(string returnUrl = null)
{
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context?.IdP != null)
// if IdP is passed, then bypass showing the login screen
return ExternalLogin(context.IdP, returnUrl);
if(context != null)
{
var currentClient = await _clientStore.FindClientByIdAsync(context.ClientId);
if (currentClient != null)
{
ViewData["ClientName"] = currentClient.ClientName;
ViewData["LogoUri"] = currentClient.LogoUri;
}
}
ViewData["ReturnUrl"] = returnUrl;
return View();
}

Related

Is there a way to avoid using the redirected form in Spring OAuth2 Authorization server when trying to get Authorization code? [duplicate]

I'm trying to create a local Java-based client that interacts with the SurveyMonkey API.
SurveyMonkey requires a long-lived access token using OAuth 2.0, which I'm not very familiar with.
I've been googling this for hours, and I think the answer is no, but I just want to be sure:
Is it possible for me to write a simple Java client that interacts with the SurveyMonkey, without setting up my own redirect server in some cloud?
I feel like having my own online service is mandatory to be able to receive the bearer tokens generated by OAuth 2.0. Is it possible that I can't have SurveyMonkey send bearer tokens directly to my client?
And if I were to set up my own custom Servlet somewhere, and use it as a redirect_uri, then the correct flow would be as follows:
Java-client request bearer token from SurveyMonkey, with
redirect_uri being my own custom servlet URL.
SurveyMonkey sends token to my custom servlet URL.
Java-client polls custom servlet URL until a token is available?
Is this correct?
Yes, it is possible to use OAuth2 without a callback URL.
The RFC6749 introduces several flows. The Implicit and Authorization Code grant types require a redirect URI. However the Resource Owner Password Credentials grant type does not.
Since RFC6749, other specifications have been issued that do not require any redirect URI:
RFC7522: Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants
RFC7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
RFC8628: OAuth 2.0 Device Authorization Grant
In any case, if the grant types above do not fit on your needs, nothing prevent you from creating a custom grant type.
Not exactly, the whole point of the OAuth flow is that the user (the client you're accessing the data on behalf of) needs to give you permission to access their data.
See the authentication instructions. You need to send the user to the OAuth authorize page:
https://api.surveymonkey.net/oauth/authorize?api_key<your_key>&client_id=<your_client_id>&response_type=code&redirect_uri=<your_redirect_uri>
This will show a page to the user telling them which parts of their account you are requesting access to (ex. see their surveys, see their responses, etc). Once the user approves that by clicking "Authorize" on that page, SurveyMonkey will automatically go to whatever you set as your redirect URI (make sure the one from the url above matches with what you set in the settings for your app) with the code.
So if your redirect URL was https://example.com/surveymonkey/oauth, SurveyMonkey will redirect the user to that URL with a code:
https://example.com/surveymonkey/oauth?code=<auth_code>
You need to take that code and then exchange it for an access token by doing a POST request to https://api.surveymonkey.net/oauth/token?api_key=<your_api_key> with the following post params:
client_secret=<your_secret>
code=<auth_code_you_just_got>
redirect_uri=<same_redirect_uri_as_before>
grant_type=authorization_code
This will return an access token, you can then use that access token to access data on the user's account. You don't give the access token to the user it's for you to use to access the user's account. No need for polling or anything.
If you're just accessing your own account, you can use the access token provided in the settings page of your app. Otherwise there's no way to get an access token for a user without setting up your own redirect server (unless all the users are in the same group as you, i.e. multiple users under the same account; but I won't get into that). SurveyMonkey needs a place to send you the code once the user authorizes, you can't just request one.
You do need to implement something that will act as the redirect_uri, which does not necessarily need to be hosted somewhere else than your client (as you say, in some cloud).
I am not very familiar with Java and Servelets, but if I assume correctly, it would be something that could handle http://localhost:some_port. In that case, the flow that you describe is correct.
I implemented the same flow successfully in C#. Here is the class that implements that flow. I hope it helps.
class OAuth2Negotiator
{
private HttpListener _listener = null;
private string _accessToken = null;
private string _errorResult = null;
private string _apiKey = null;
private string _clientSecret = null;
private string _redirectUri = null;
public OAuth2Negotiator(string apiKey, string address, string clientSecret)
{
_apiKey = apiKey;
_redirectUri = address.TrimEnd('/');
_clientSecret = clientSecret;
_listener = new HttpListener();
_listener.Prefixes.Add(address + "/");
_listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
}
public string GetToken()
{
var url = string.Format(#"https://api.surveymonkey.net/oauth/authorize?redirect_uri={0}&client_id=sm_sunsoftdemo&response_type=code&api_key=svtx8maxmjmqavpavdd5sg5p",
HttpUtility.UrlEncode(#"http://localhost:60403"));
System.Diagnostics.Process.Start(url);
_listener.Start();
AsyncContext.Run(() => ListenLoop(_listener));
_listener.Stop();
if (!string.IsNullOrEmpty(_errorResult))
throw new Exception(_errorResult);
return _accessToken;
}
private async void ListenLoop(HttpListener listener)
{
while (true)
{
var context = await listener.GetContextAsync();
var query = context.Request.QueryString;
if (context.Request.Url.ToString().EndsWith("favicon.ico"))
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.Close();
}
else if (query != null && query.Count > 0)
{
if (!string.IsNullOrEmpty(query["code"]))
{
_accessToken = await SendCodeAsync(query["code"]);
break;
}
else if (!string.IsNullOrEmpty(query["error"]))
{
_errorResult = string.Format("{0}: {1}", query["error"], query["error_description"]);
break;
}
}
}
}
private async Task<string> SendCodeAsync(string code)
{
var GrantType = "authorization_code";
//client_secret, code, redirect_uri and grant_type. The grant type must be set to “authorization_code”
var client = new HttpClient();
client.BaseAddress = new Uri("https://api.surveymonkey.net");
var request = new HttpRequestMessage(HttpMethod.Post, string.Format("/oauth/token?api_key={0}", _apiKey));
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("client_secret", _clientSecret));
formData.Add(new KeyValuePair<string, string>("code", code));
formData.Add(new KeyValuePair<string, string>("redirect_uri", _redirectUri));
formData.Add(new KeyValuePair<string, string>("grant_type", GrantType));
formData.Add(new KeyValuePair<string, string>("client_id", "sm_sunsoftdemo"));
request.Content = new FormUrlEncodedContent(formData);
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_errorResult = string.Format("Status {0}: {1}", response.StatusCode.ToString(), response.ReasonPhrase.ToString());
return null;
}
var data = await response.Content.ReadAsStringAsync();
if (data == null)
return null;
Dictionary<string, string> tokenInfo = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return(tokenInfo["access_token"]);
}
}

How can I log the generated Access Token in Identity Server 4?

I would like to know how we can log the generated Refresh & AccessToken in IdentityServer 4.
Currently, we've got the custom implementation about the JwtAccessToken and we writes it + userId/name to the central logging system whenever it generates a new Access token. For Apis (we've more than 10), it always writes all incoming requests + JwtToken to the same logging system. So, we can easily trace what the user had done and see the logs/values at that particular time.
Now, we are going to replace that custom security implementation with IDSV4 and we couldn't find out a way to log the generated token in IDSV4.
We know that we can get the Access Token in .Net App by using await HttpContext.GetAccessTokenAsync(). But we don't want to manually send a log from all our apps (.Net, Spas, Apis (Client Credentials)) which are going to integrate with IDSV. We want to manage that AccessToken logging in a central place as we did before.
I looked at the IDSV4 sourcecode TokenEndpoint.cs Line120, LogTokens()
if (response.IdentityToken != null)
{
_logger.LogTrace("Identity token issued for {clientId} / {subjectId}: {token}", clientId, subjectId, response.IdentityToken);
}
if (response.RefreshToken != null)
{
_logger.LogTrace("Refresh token issued for {clientId} / {subjectId}: {token}", clientId, subjectId, response.RefreshToken);
}
if (response.AccessToken != null)
{
_logger.LogTrace("Access token issued for {clientId} / {subjectId}: {token}", clientId, subjectId, response.AccessToken);
}
Actually, they write the TraceLogs for the actual tokens. But we don't want to update the log level to Trace because it'll flood our logging system.
So, I would like to know whether it's possible to implement a feature to write a generated tokens to a log whenever IDSV4 issues an AccessToken. Is there anyway to intercept these tokens after the generation?
Or do we have to manually log AccessTokens whenever it's generated or refreshed in all our clients?
Update:
Thanks to sellotape for giving me an idea for DI. The following is the correct class to intercept the generated Token:
public class CustomTokenResponseGenerator : TokenResponseGenerator
{
public CustomTokenResponseGenerator(ISystemClock clock, ITokenService tokenService, IRefreshTokenService refreshTokenService, IResourceStore resources, IClientStore clients, ILogger<TokenResponseGenerator> logger) : base(clock, tokenService, refreshTokenService, resources, clients, logger)
{
}
public override async Task<TokenResponse> ProcessAsync(TokenRequestValidationResult request)
{
var result = await base.ProcessAsync(request);
// write custom loggings here
return result;
}
}
After that you can replace default class from IDSV4 with your custom class
services.Replace(ServiceDescriptor.Transient<ITokenResponseGenerator, CustomTokenResponseGenerator>());
There are many places to hook in for this; one is to create your own implementation of ITokenService by deriving from DefaultTokenService.
Override CreateAccessTokenAsync() and have it do:
Token result = await base.CreateAccessTokenAsync(request);
// Your logging goes here...
return result;
Swap in your version in your DI container at Startup (make sure it's after the default one has already been added):
services.Replace<ITokenService, MyTokenService>();
... and you should be ready.
As an aside, you should really log hashes of your tokens and not the tokens themselves. You can still match requests and actions to users based on the hash, but then at least nobody will be able to use the logging data to impersonate any of your users.

Validate AppEngine Endpoints Client IDs while using custom Authenticator

Earlier our client side apps used Google Sign-In.
Now we are moving to custom auth, as we plan on having the user's phone number as the only identity (instead of a Google Account). But after implementing the custom Authenticator, the client IDs are not being checked and I am able to make API calls from anywhere.
When only Google Sign-in was being used at the client side, the client ID was being validated and I was not able to make API calls from any clients other than the ones authorized.
How do I verify the Client IDs while using custom authenticator?
Code for the Api Endpoint
#Api(name = "apiSubscriber",
clientIds = {
Constants.webClientId,
Constants.androidClientId,
Constants.iOSClientId
},
authenticators = {com.google.api.server.spi.auth.EndpointsAuthenticator.class,
CustomAuth.class},
audiences = {Constants.androidAudience},
)
public class ApiSubscriber {
#ApiMethod
public Subscriber getSubscriberData(User user){
if(user!=null){
//fetches subscriber data
}
}
//... Other ApiMethods
}
Code for Custom Authenticator
public class CustomAuth implements Authenticator {
#Override
public User authenticate(HttpServletRequest request) {
String phoneNumber = request.getHeader("phoneNumber");
String token = request.getHeader("Authorization");
if(checkToken(phoneNumber,token)){
return new User(phoneNumber);
}
return null;
}
private boolean checkToken(String phoneNumber, String token){
//Checks if authorization token is valid
}
}
Unfortunately at this time, it does not appear that you can restrict your Endpoints API to a client and not use Google Sign in.
When using Google's oAuth2 authentication some magic voodoo happens (not exactly sure what) and apps get restricted to the ClientId's that you specify.
However, when you stop using that authentication method, I have found (to my dear disappointment), that it does not work anymore.
See my question here where you can read about my tests and some additional things that may give you more information: Authenticating your client to Cloud Endpoints without a Google Account login
I don't sure is it a problem, but you have some bugs in code you provided.
authenticators = {com.google.api.server.spi.auth.EndpointsAuthenticator.class,
CustomAuth.class,
instead of comma must be bracket. Also, imho, you need only CustomAuth class here.
audiences = {Constants.androidAudience},
comma is redundant.
Second. You don't required to use custom Authenticator. You can send token and phone number as concatenated parameter or two parameters to your service method and check it there.

OWIN Invalid URI: The Uri String is too long

I have an MVC application hosted on a server (IIS) which points to 3 SQL databases. This has been running without issues for months.
I've just had to change the connectionstrings for all 3 SQL databases to point to new databases.
Now when I try to log in I get the following error..
The connection strings are using Windows Authentication and this account is set in the AppPool. I've also manually tried to connect to each database instance with the account and this works fine. I'm beginning to think the change is SQL connections is just a red herring.
In terms of the error message, I totally understand what the error is Im just not sure why its being thrown. The only thing I can think of is I'm in some kind of redirect loop which is appending the URL.
It definitely feels like an IIS issue but I can't put my finger on it.
Has anyone come across this before with OWIN or can advise on debugging steps that might diagnose the issue?
Startup.cs
public partial class Startup
{
private static bool IsAjaxRequest(IOwinRequest request)
{
IReadableStringCollection query = request.Query;
if ((query != null) && (query["X-Requested-With"] == "XMLHttpRequest"))
{
return true;
}
IHeaderDictionary headers = request.Headers;
return ((headers != null) && (headers["X-Requested-With"] == "XMLHttpRequest"));
}
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ParentDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.CreatePerOwinContext(PrincipalManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity =
SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, Guid>(
TimeSpan.FromMinutes(int.Parse(WebConfigurationManager.AppSettings["RefreshInterval"])),
(manager, user) => manager.GenerateUserIdentityAsync(user),
claim => new Guid(claim.GetUserId())),
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
}
}
});
}
}
After hours of investigation I eventually found the issue.
The issue was the number of claims being added for a user. Once we reduced the number of claims it started working again.
The most likely cause is that you're stuck in an error loop. If the authentication to the database where the users is stored is failing then you will get sent to the error page which will try to run the authentication again and fail and send you to the error page, again and again. Each pass it would append to the previous url eventually reaching this state.

How do I test Cloud Endpoints with Oauth on devserver

My app uses Oauthed Cloud Endpoints and is working fine in production.
My problem is that on the local devserver, my User user is always set to example#example.com, even though I've gone through the usual auth, access code, etc etc etc and have a valid authed user.
I get that example#example.com is useful to test oauth endpoints before I have oauth working properly, but since my app is working I'd rather see the actual user there.
To be specific, my endpoint method is
#ApiMethod(name = "insertEmp"), etc
public Emp insertEmp(User user, Emp emp) {
System.out.println(user.getEmail()); // (A) log "appengine" email
System.out.println(OAuthServiceFactory.getOAuthService().getCurrentUser().getEmail(); // (B) log authed email
...
When deployed, everything is fine, and both (A) and (B) log the authenticated user (my.email#gmail.com).
When testing on my local devserver, (A) always logs "example#example.com", even though I have gone through the Oauth sequence and have a valid, authenticated user, and (B) logs my.email#gmail.com. So I can do hi-fidelity testing, I need the User to be the real authenticated user.
So in simple terms, how do I get (A) and (B) to be the same?
It seems it can't be done. I've ended up coding around it by putting the following code at the top of my Endpoint methods.
if ("example#example.com".equalsIgnoreCase(user.getEmail()) {
user = new User(OAuthServiceFactory.getOAuthService().getCurrentUser().getEmail(),"foo");
}
So now, even on devserver, the User email matches the Oauth email.
This is not so easy. You'll have to make your settings in the APIs Console. Here you will be able to add "localhost" (http://localhost/) Then you can authenticate, through Google, even though you are running you application on your localhost for development.
I have used it extensively, and it works OK
Links: https://code.google.com/apis/console/
Just remember the ID's you use here is completely independent of you appengine ID.
Took me a few hours to figure that one out.
The thing is that when you are doing the authentication in local, you are not doing it through the Google servers so authenticating your user is something that actually is not happening in local.
Google always provides the example#example.com user when you try to simulate the log in, it happens basically in all the services, like when you provide a log in through your Google Account in any web site (for instance using GWT and App Engine).
What can be different in your site if you test with your real user or you consider example#example.com user as your user?
In your endpoint API you need this
ApiMethod ( name="YourEndPointName", path="yourPath",
clientIds={"YourId.apps.googleusercontent.com"},
scopes = { "https://www.googleapis.com/auth/userinfo.profile" })
Then in the called method, you will have a User object from the GAPI.
Use this to get the actual email from the google User object like this
public myEndPointMethod( Foo foo, User user ){
email = user.getEmail();
}
I replaced the Oauth2 user (example#example.com) with user from UserFactory and it works fine. I use this method to validate user for all API authenticated API requests.
public static User isAuthenticated(User user) throws OAuthRequestException{
if(user == null){
throw new OAuthRequestException("Please login before making requests");
}
if(SystemProperty.environment.value() ==
SystemProperty.Environment.Value.Development && "example#example.com".equalsIgnoreCase(user.getEmail()) ) {
//Replace the user from the user factory here.
user = UserServiceFactory.getUserService().getCurrentUser();
}
return user;
}
Using the go runtime I have resorted to this function to obtain a User that is functional on both the dev server and production:
func currentUser(c context.Context) *user.User {
const scope = "https://www.googleapis.com/auth/userinfo.email"
const devClient = "123456789.apps.googleusercontent.com"
allowedClients := map[string]bool{
"client-id-here.apps.googleusercontent.com": true,
devClient: true, // dev server
}
usr, err := user.CurrentOAuth(c, scope)
if err != nil {
log.Printf("Warning: Could not get current user: %s", err)
return nil
}
if !allowedClients[usr.ClientID] {
log.Printf("Warning: Unauthorized client connecting with the server: %s", usr.ClientID)
return nil
}
if (usr.ClientID == devClient) {
usr = user.Current(c) // replace with a more interesting user for dev server
}
return usr
}
This will use the dev server login information entered using http://localhost:8080/_ah/login
It's not possible.
I use another endpoint to replace user_id in current session.

Resources