OWIN Invalid URI: The Uri String is too long - sql-server

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.

Related

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.

Login page customized depending on client

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();
}

Azure Active Directory: Consent Framework has stopped granting consent

I have just written an app with Azure Active Directory Single Sign-On.
The consent framework is handled in the AccountController, in the ApplyForConsent action listed below. Until recently, everything has worked seamlessly. I could grant consent as an admin user of an external tenant, then sign out of the app, and sign in again as a non-admin user.
My Azure Active Directory app requires the following delegated permissions:
Read directory data
Enable sign-on and read users' profiles
Access your organisation's directory
Now, after I have gone through the consent framework (by POSTing from a form to ApplyForConsent as an admin user), signing in as a non-admin user fails with the error message AADSTS90093 (This operation can only be performed by an administrator). Unhelpfully, the error message doesn't say what "this operation" actually is, but I suspect it is the third one (Access your organisation's directory).
I stress again, this has only recently stopped working. Nothing has changed in this part of the code, although I grant it is possible that other changes elsewhere in the codebase may have had knock-on effects of which I remain blissfully ignorant.
Looking at the documentation, it seems that this use of the consent framework is already considered "Legacy", but I'm having a difficult time finding a more up-to-date implementation.
The requested permissions in the code sample below is the single string "DirectoryReaders".
I have three questions for helping me debug this code:
What is the difference between "Read directory data" and "Access your organisation's directory"? When would I need one rather than another?
Do I need to request more than just "DirectoryReaders"?
Is there now a better way to implement the Consent Framework?
This is the existing code:
private static readonly string ClientId = ConfigurationManager.AppSettings["ida:ClientID"];
[HttpPost]
public ActionResult ApplyForConsent()
{
string signupToken = Guid.NewGuid().ToString();
string replyUrl = Url.Action("ConsentCallback", "Account", new { signupToken }, Request.Url.Scheme);
DatabaseIssuerNameRegistry.CleanUpExpiredSignupTokens();
DatabaseIssuerNameRegistry.AddSignupToken(signupToken, DateTimeOffset.UtcNow.AddMinutes(5));
return new RedirectResult(CreateConsentUrl(ClientId, "DirectoryReaders", replyUrl));
}
[HttpGet]
public ActionResult ConsentCallback()
{
string tenantId = Request.QueryString["TenantId"];
string signupToken = Request.QueryString["signupToken"];
if (DatabaseIssuerNameRegistry.ContainsTenant(tenantId))
{
return RedirectToAction("Validate");
}
string consent = Request.QueryString["Consent"];
if (!String.IsNullOrEmpty(tenantId) && String.Equals(consent, "Granted", StringComparison.OrdinalIgnoreCase))
{
if (DatabaseIssuerNameRegistry.TryAddTenant(tenantId, signupToken))
{
return RedirectToAction("Validate");
}
}
const string error = "Consent could not be provided to your Active Directory. Please contact SharpCloud for assistance.";
var reply = Request.Url.GetLeftPart(UriPartial.Authority) + Url.Action("Consent", new { error });
var config = FederatedAuthentication.FederationConfiguration.WsFederationConfiguration;
var signoutMessage = new SignOutRequestMessage(new Uri(config.Issuer), reply);
signoutMessage.SetParameter("wtrealm", config.Realm);
FederatedAuthentication.SessionAuthenticationModule.SignOut();
return Redirect(signoutMessage.WriteQueryString());
}
private static string CreateConsentUrl(string clientId, string requestedPermissions, string consentReturnUrl)
{
string consentUrl = String.Format(CultureInfo.InvariantCulture, ConsentUrlFormat, HttpUtility.UrlEncode(clientId));
if (!String.IsNullOrEmpty(requestedPermissions))
{
consentUrl += "&RequestedPermissions=" + HttpUtility.UrlEncode(requestedPermissions);
}
if (!String.IsNullOrEmpty(consentReturnUrl))
{
consentUrl += "&ConsentReturnURL=" + HttpUtility.UrlEncode(consentReturnUrl);
}
return consentUrl;
}
I think this link addresses your issue:
http://blogs.msdn.com/b/aadgraphteam/archive/2015/03/19/update-to-graph-api-consent-permissions.aspx
The quick summary is that now only admins can grant consent to a web app for '•Access your organisation's directory'.
This change was made back in March. Native apps are not affected by the change.
My suspicion was correct. I was using legacy tech in the question. By moving to Owin and Identity 2.0, all issues were solved.
The new approach is summarised by https://github.com/AzureADSamples/WebApp-GroupClaims-DotNet

Building realtime app using Laravel and Latchet websocket

I'm building a closed app (users need to authenticate in order to use it). I'm having trouble in identifying the currently authenticated user from my Latchet session. Since apache does not support long-lived connections, I host Latchet on a separate server instance. This means that my users receive two session_id's. One for each connection. I want to be able to identify the current user for both connections.
My client code is a SPA based on AngularJS. For client WS, I'm using the Autobahn.ws WAMP v1 implementation. The ab framework specifies methods for authentication: http://autobahn.ws/js/reference_wampv1.html#session-authentication, but how exactly do I go about doing this?
Do I save the username and password on the client and retransmit these once login is performed (which by the way is separate from the rest of my SPA)? If so, won't this be a security concearn?
And what will receive the auth request server side? I cannot find any examples of this...
Please help?
P.S. I do not have reputation enough to create the tag "Latchet", so I'm using Ratchet (which Latchet is built on) instead.
Create an angularjs service called AuthenticationService, inject where needed and call it with:
AuthenticationService.check('login_name', 'password');
This code exists in a file called authentication.js. It assumes that autobahn is already included. I did have to edit this code heavily removing all the extra crap I had in it,it may have a syntax error or two, but the idea is there.
angular.module(
'top.authentication',
['top']
)
.factory('AuthenticationService', [ '$rootScope', function($rootScope) {
return {
check: function(aname, apwd) {
console.log("here in the check function");
$rootScope.loginInfo = { channel: aname, secret: apwd };
var wsuri = 'wss://' + '192.168.1.11' + ':9000/';
$rootScope.loginInfo.wsuri = wsuri;
ab.connect(wsuri,
function(session) {
$rootScope.loginInfo.session = session;
console.log("connected to " + wsuri);
onConnect(session);
},
function(code,reason) {
$rootScope.loginInfo.session = null;
if ( code == ab.CONNECTION_UNSUPPORTED) {
console.log(reason);
} else {
console.log('failed');
$rootScope.isLoggedIn = 'false';
}
}
);
function onConnect(sess) {
console.log('onConnect');
var wi = $rootScope.loginInfo;
sess.authreq(wi.channel).then(
function(challenge) {
console.log("onConnect().then()");
var secret = ab.deriveKey(wi.secret,JSON.parse(challenge).authextra);
var signature = sess.authsign(challenge, secret);
sess.auth(signature).then(onAuth, ab.log);
},ab.log
);
}
function onAuth(permission) {
$rootScope.isLoggedIn = 'true';
console.log("authentication complete");
// do whatever you need when you are logged in..
}
}
};
}])
then you need code (as you point out) on the server side. I assume your server side web socket is php coding. I can't help with that, haven't coded in php for over a year. In my case, I use python, I include the autobahn gear, then subclass WampCraServerProtocol, and replace a few of the methods (onSessionOpen, getAuthPermissions, getAuthSecret, onAuthenticated and onClose) As you can envision, these are the 'other side' of the angular code knocking at the door. I don't think autobahn supports php, so, you will have to program the server side of the authentication yourself.
Anyway, my backend works much more like what #oberstat describes. I establish authentication via old school https, create a session cookie, then do an ajax requesting a 'ticket' (which is a temporary name/password which i associate with the web authenticated session). It is a one use name/password and must be used in a few seconds or it disappears. The point being I don't have to keep the user's credentials around, i already have the cookie/session which i can create tickets that can be used. this has a neat side affect as well, my ajax session becomes related to my web socket session, a query on either is attributed to the same session in the backend.
-g
I can give you a couple of hints regarding WAMP-CRA, which is the authentication mechnism this is referring:
WAMP-CRA does not send passwords over the wire. It works by a challenge-response scheme. The client and server have a shared secret. To authenticate a client, the server will send a challenge (something random) that the client needs to sign - using the secret. And only the signature is sent back. The client might store the secret in browser local storage. It's never sent.
In a variant of above, the signing of the challenge the server sends is not directly signed within the client, but the client might let the signature be created from an Ajax request. This is useful when the client was authenticated using other means already (e.g. classical cookie based), and the signing can then be done in the classical web app that was authenticating.
Ok, Greg was kind enough to provide a full example of the client implementation on this, so I wont do anything more on that. It works with just a few tweaks and modifications to almost any use-case I can think of. I will mark his answer as the correct one. But his input only covered the theory of the backend implementation, so I will try to fill in the blanks here for postparity.
I have to point out though, that the solution here is not complete as it does not give me a shared session between my SPA/REST connection and my WS connection.
I discovered that the authentication request transmitted by autobahn is in fact a variant of RPC and for some reason has hardcoded topic names curiously resembling regular url's:
- 'http://api.wamp.ws/procedure#authreq' - for auth requests
- 'http://api.wamp.ws/procedure#auth' - for signed auth client responses
I needed to create two more routes in my Laravel routes.php
// WS CRA routes
Latchet::topic('http://api.wamp.ws/procedure#authreq', 'app\\socket\\AuthReqController');
Latchet::topic('http://api.wamp.ws/procedure#auth', 'app\\socket\\AuthReqController');
Now a Latchet controller has 4 methods: subscribe, publish, call and unsubscribe. Since both the authreq and the auth calls made by autobahn are RPC calls, they are handled by the call method on the controller.
The solution first proposed by oberstet and then backed up by Greg, describes a temporary auth key and secret being generated upon request and held temporarily just long enough to be validated by the WS CRA procedure. I've therefore created a REST endpoint which generates a persisted key value pair. The endpoint is not included here, as I am sure that this is trivial.
class AuthReqController extends BaseTopic {
public function subscribe ($connection, $topic) { }
public function publish ($connection, $topic, $message, array $exclude, array $eligible) { }
public function unsubscribe ($connection, $topic) { }
public function call ($connection, $id, $topic, array $params) {
switch ($topic) {
case 'http://api.wamp.ws/procedure#authreq':
return $this->getAuthenticationRequest($connection, $id, $topic, $params);
case 'http://api.wamp.ws/procedure#auth':
return $this->processAuthSignature($connection, $id, $topic, $params);
}
}
/**
* Process the authentication request
*/
private function getAuthenticationRequest ($connection, $id, $topic, $params) {
$auth_key = $params[0]; // A generated temporary auth key
$tmpUser = $this->getTempUser($auth_key); // Get the key value pair as persisted from the temporary store.
if ($tmpUser) {
$info = [
'authkey' => $tmpUser->username,
'secret' => $tmpUser->secret,
'timestamp' => time()
];
$connection->callResult($id, $info);
} else {
$connection->callError($id, $topic, array('User not found'));
}
return true;
}
/**
* Process the final step in the authentication
*/
private function processAuthSignature ($connection, $id, $topic, $params) {
// This should do something smart to validate this response.
// The session should be ours right now. So store the Auth::user()
$connection->user = Auth::user(); // A null object is stored.
$connection->callResult($id, array('msg' => 'connected'));
}
private function getTempUser($auth_key) {
return TempAuth::findOrFail($auth_key);
}
}
Now somewhere in here I've gone wrong. Cause if I were supposed to inherit the ajax session my app holds, I would be able to call Auth::user() from any of my other WS Latchet based controllers and automatically be presented with the currently logged in user. But this is not the case. So if somebody see what I'm doing wrong, give me a shout. Please!
Since I'm unable to get the shared session, I'm currently cheating by transmitting the real username as a RPC call instead of performing a full CRA.

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