MVC 5 OAuth with CORS - ajax

I read several post talking about some similar problems, but I don't get yet to do this to work.
I'm doing ajax to "Account/ExternalLogin" which generates the ChallengeResult and starts the flow for the authentication with OWIN.
This is my Startup class :
public partial class Startup
{
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseCors(CorsOptions.AllowAll);
var goath2 = new Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticationOptions
{
ClientId = "myclientid",
ClientSecret = "mysecret",
Provider = new Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticationProvider
{
OnApplyRedirect = context =>
{
string redirect = context.RedirectUri;
const string Origin = "Origin";
const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
// origin is https://localhost:44301
var origin = context.Request.Headers.GetValues(Origin).First();
// header is present
var headerIsPresent = context.Response.Headers.ContainsKey(AccessControlAllowOrigin);
context.Response.Redirect(redirect);
}
}
};
app.UseGoogleAuthentication(goath2);
}
}
I'm enabling CORS support whith the line app.UserCors(CorsOptinos.AllowAll);
And I know the header is being added to the response because I intercept the OnApplyRedirectevent and when I look for the origin it is setted to 'localhost:443001' and the header 'Access-Control-Allow-Origin' is setted also to this value.
Nevertheless when the response is sent to the client I have the following error:
XMLHttpRequest cannot load
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=xxxxxxxxxxxxxxxxxxxxxxxxxxx
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin https://localhost:44301 is therefore not allowed
access.
What I'm missing here.
I could get a work around doing all this "manually" (requesting directly google from the client...) but I really want to use the OWIN middleware.

You are making request to google from https://localhost:44301 domain. In order that to work 'Access-Control-Allow-Origin' should have 'https://localhost:44301' origin domain in the list. So in this case it's google who need to set this domain in the 'Access-Control-Allow-Origin'.
Looking at the response you are getting it seems that google doesn't allow Cros origin request from your domain. To solve this I believe you need to register your domain on google https://developers.google.com.

Related

How to modify Response Cookie in GraphQL using Hot Chocolate in .Net 5

I am building a GraphQL API using Hot Chocolate(.net 5) and need to add authentication using the JWT token.
In REST API, I have used http only cookie to add the refresh token.
var cookieOption = new CookieOptions
{
HttpOnly = true,
Expires = DateTime.UtcNow.AddDays(7)
};
Response.Cookies.Append("refreshToken", <refreshToken.Token>, cookieOption);
In my login mutation, I do not have access to HttpResponse as in REST API.
Even Hot Chocolate's documentation does not have an example or instruction on how to access the Http Response.
I highly appreciate any help on this.
Thanks
You can use the IHttpContextAccessor to access the HttpContext and in turn modify the cookies.
public string Foo(string id, [Service] IHttpContextAccessor httpContextAccessor)
{
if (httpContextAccessor.HttpContext is not null)
{
httpContextAccessor.HttpContext.Response.Cookies...
}
}
https://chillicream.com/docs/hotchocolate/fetching-data/resolvers/#ihttpcontextaccessor

Login to web server with Google Sign-in

I'm connecting to a 3rd party web server from an HTTP client (Java or Dart - Android app) to download some resources (XML or IMG files) that belong to the current user on that server. This site requires login with Google Sing-In. I have everything set up in my Android app to login the user with Google, I obtained their authorization idToken. But how do actually use it in HTTP GET or POST methods to download the protected resources?
With BASIC authentication it's easy - just set HTTP 'Authorization' header correctly ("Basic " + user:password encoded as base64), call GET, and I download the desired resource. But I cannot find any information on how to do this with Google Sing-In. Do I send the idToken I received from Google in some headers? What other magic is needed?
Adding a Java code snippet, hope it helps:
// (Receive authCode via HTTPS POST)
if (request.getHeader('X-Requested-With') == null) {
// Without the `X-Requested-With` header, this request could be forged. Aborts.
}
// Set path to the Web application client_secret_*.json file you downloaded from the
// Google API Console: https://console.developers.google.com/apis/credentials
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "/path/to/client_secret.json";
// Exchange auth code for access token
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(
JacksonFactory.getDefaultInstance(), new FileReader(CLIENT_SECRET_FILE));
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
authCode,
REDIRECT_URI) // Specify the same redirect URI that you use with your web
// app. If you don't have a web version of your app, you can
// specify an empty string.
.execute();
String accessToken = tokenResponse.getAccessToken();
// Use access token to call API
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Drive drive =
new Drive.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
.setApplicationName("Auth Code Exchange Demo")
.build();
File file = drive.files().get("appfolder").execute();
// Get profile info from ID token
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String userId = payload.getSubject(); // Use this value as a key to identify a user.
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
String name = (String) payload.get("name");
String pictureUrl = (String) payload.get("picture");
String locale = (String) payload.get("locale");
String familyName = (String) payload.get("family_name");
String givenName = (String) payload.get("given_name");
For detailed info, find all the required steps and references at: https://developers.google.com/identity/sign-in/web/server-side-flow#step_1_create_a_client_id_and_client_secret

websockets and authentication with identityserver4

I am using .net core 1.1 and identityserver 4 to get tokens and validate users. The web api works fine reading the bearer token from the headers and getting the user principal claims.
Now I want to use a websocket (not SignalR) for sending notifications. I can open a ws:// channel (or wss) but token isn't sent with the headers, so in the .net core application I have no information of the user (User Claims and Identity).
How can I authenticate the user through the websocket? I did a search but couldn't find any helpful information.
Thanks
There are two main problems related to the authentication in WebSocket middleware:
Authorization should be called manually
First of all, authorization is not applied to web socket request (as it is not a controller which can be marked with Authorize attribute).
That's why in WebSocket middleware you need to call authorization by your self. This is easy to achieve by calling AuthenticateAsync extension method of the HttpContext object.
So, your middleware will be look something like this:
public class WebSocketMiddleware
{
private readonly RequestDelegate next;
public WebSocketMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await this.next.Invoke(context);
return;
}
AuthenticateResult authenticateResult =
await context.AuthenticateAsync(OAuthValidationDefaults.AuthenticationScheme);
....
});
}
So, using authentication results you can check if the user is authenticated or not and then access authenticated user information.
Passing bearer token to web sockets request
For Web Socket connections, the default Authorization header does not work, because the WebSockets JS API doesn't allow setting custom parameters. To work around this limitation, the access token is passed quite often in the query string.
To make authentication middleware to use it, you need to update authentication validation options. This basically can be done in your startup script like this:
services
.AddAuthentication()
.AddOAuthValidation(options =>
{
options.Events = new OAuthValidationEvents
{
// Note: for Web Socket connections, the default Authorization header does not work,
// because the WebSockets JS API doesn't allow setting custom parameters.
// To work around this limitation, the access token is retrieved from the query string.
OnRetrieveToken = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.FromResult(0);
}
};
});
The following code can be used as an example to add access token to web socket url during connection initializing:
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
const wsUri = protocol + "//" + window.location.host + "/ws" + "?access_token=" + token;
this.socket = new WebSocket(wsUri);

ServiceStack user session not found when using sessionId in client Headers or Cookies

I am using ServiceStack v4 with custom Authentication. This is setup and working correctly. I can call the /auth service and get a returned AuthorizationResponse with unique SessionId.
I also have swagger-ui plugin setup. Using it, I can authenticate via /auth and then call one of my other services which require authentication without issue.
Now, from a secondary MVC application using the c# JsonServiceClient I can again successfully make a call to /auth and then secured services using the same client object. However, if I dispose of that client (after saving the unique sessionId to a cookie), then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401. If I call a non-secure service, but then try to access the unique user session, it returns a new session.
If I look at the request headers in that service, the cookie or Header is clearly set with the sessionId. The sessionId also exists in the sessionCache. The problem seems to be that the code which tries to get the session from the request isn't finding it.
To be more specific, it appears that ServiceExtensions.GetSessionId is looking at the HostContext and not the calling Request. I'm not sure why. Perhaps I misunderstand something along the way here.
If I directly try and fetch my expected session with the following code it's found without issue.
var req = base.Request;
var sessionId = req.GetHeader("X-" + SessionFeature.PermanentSessionId);
var sessionKey = SessionFeature.GetSessionKey(sessionId);
var session = (sessionKey != null ? Cache.Get<IAuthSession>(sessionKey) : null)?? SessionFeature.CreateNewSession(req, sessionId);
So, am I missing something obvious here? Or maybe not so obvious in creating my secondary client?
Sample code of client calls
Here is my authorization code. It's contained in a Controller class. This is just the relevant parts.
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
try
{
loginResult = client.Post(new Authenticate()
{
UserName = model.Email,
Password = model.Password,
RememberMe = model.RememberMe
});
Response.SetCookie(new HttpCookie(SessionFeature.PermanentSessionId, loginResult.SessionId));
return true;
}
}
Here is my secondary client setup and service call, contained in it's own controller class in another area of the MVC application
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
var cCookie = HttpContext.Request.Cookies.Get(SessionFeature.PermanentSessionId);
if (cCookie != null)
{
client.Headers.Add("X-" + SessionFeature.PermanentSessionId, cCookie.Value);
client.Headers.Add("X-" + SessionFeature.SessionOptionsKey, "perm");
}
response = client.Get(new SubscriptionStatusRequest());
}
Additional Update
During the Authenticate process the following function is called from HttpRequestExtensions with the name = SessionFeature.PermanentSessionId
public static class HttpRequestExtensions
{
/// <summary>
/// Gets string value from Items[name] then Cookies[name] if exists.
/// Useful when *first* setting the users response cookie in the request filter.
/// To access the value for this initial request you need to set it in Items[].
/// </summary>
/// <returns>string value or null if it doesn't exist</returns>
public static string GetItemOrCookie(this IRequest httpReq, string name)
{
object value;
if (httpReq.Items.TryGetValue(name, out value)) return value.ToString();
Cookie cookie;
if (httpReq.Cookies.TryGetValue(name, out cookie)) return cookie.Value;
return null;
}
Now what occurs is the httpReq.Items contains a SessionFeature.PermanentSessionId value, but I have no clue why and where this gets set. I don't even understand at this point what the Items container is on the IRequest. The code thus never gets to the functionality to check my cookies or headers
The Session wiki describes the different cookies used by ServiceStack Session.
If the client wants to use a Permanent SessionId (i.e. ss-pid), it also needs to send a ss-opt=perm Cookie to indicate it wants to use the permanent Session. This Cookie is automatically set when authenticating with the RememberMe=true option during Authentication.
There was an issue in the Session RequestFilter that was used to ensure Session Id's were attached to the current request weren't using the public IRequest.GetPermanentSessionId() API's which also looks for SessionIds in the HTTP Headers. This has been resolved with this commit which now lets you make Session requests using HTTP Headers, e.g:
//First Authenticate to setup an Authenticated Session with the Server
var client = new JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "user",
Password = "p#55word",
RememberMe = true,
});
//Use new Client instance without Session Cookies populated
var clientWithHeaders = new JsonServiceClient(BaseUrl);
clientWithHeaders.Headers["X-ss-pid"] = authResponse.SessionId;
clientWithHeaders.Headers["X-ss-opt"] = "perm";
var response = clientWithHeaders.Send(new AuthOnly()); //success
This fix is available from v4.0.37+ that's now available on MyGet.
However, if I dispose of that client (after saving the unique sessionId to a cookie)
If the client is disposed where is the cookie you are saving the sessionId located? This answer might provide some additional information.
then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401
If you store/save a valid sessionId as a string you should be able to supply it within a CookieContainer of a new client (given the sessionId is still authenticated). I know you said you tried adding the sessionId as a Cookie but I don't a see sample within your question using the CookieContainer so it should look something like...
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
var cCookieId = savedCookieId; //a string that I believe you saved from a successfully authenticated client that is now disposed
if (cCookieId != null)
{
var cookie = new Cookie(SessionFeature.PermanentSessionId, cCookieId);
//cookie.Domian = "somedomain.com" //you will probably need to supply this as well
client.CookieContainer.Add(cookie)
}
response = client.Get(new SubscriptionStatusRequest());
}

Thinktecture IdentityModel AuthenticationConfiguration Mapping for Cookie - how?

I have a Web API based application currently set up using the amazing Thinktecture IdentityModel 4.5.
It is set up for claims-based authentication, accepting a Basic auth credential sent in on the Authorization header. The javascript client saves the returned session token and uses this for subsequent requests by including it in the Authorization header preceded by Session as the scheme.
The javascript client also saves the token to a cookie, for retrieval if the window is closed and reopened quickly, or when new windows are opened to prevent the user having to re-authenticate. The cookie is named sessionToken and it's value is the actual token.
It all works wonderfully well.
The problem is I have a link on the app page that links to a direct address (/api/controller/id/pdfdocument) and opens it in a new window (target: _blank). Therefore there is no way to include the Authorization header in this request. However, the cookie is transferred over correctly as the session is still active.
I have tried to add a mapping to the AuthenticationConfig.Mappings collection to add support for collecting the token from the cookie, however I just can't get the configuration right to get this working, and havn't been able to find any other resources online. I'm assuming there's something very simple that needs to get fixed.
My code:
private static AuthenticationConfiguration CreateAuthenticationConfiguration()
{
var sessionTokenConfiguration = new SessionTokenConfiguration();
sessionTokenConfiguration.EndpointAddress = "/Authenticate";
sessionTokenConfiguration.DefaultTokenLifetime = new TimeSpan(1, 0, 0);
var authenticationConfig = new AuthenticationConfiguration
{
ClaimsAuthenticationManager = _authenticationManager,
RequireSsl = false,
EnableSessionToken = true,
SessionToken = sessionTokenConfiguration,
SendWwwAuthenticateResponseHeaders = false
};
var securityTokenHandler = new Thinktecture.IdentityModel.Tokens.Http.BasicAuthenticationWithRoleSecurityTokenHandler(_userService.ValidateUser, _userService.GetRolesForUser);
securityTokenHandler.RetainPassword = false;
var realm = "localhost";
var authorizationMapping = new AuthenticationOptionMapping
{
Options = AuthenticationOptions.ForAuthorizationHeader(scheme: "Basic"),
TokenHandler = new System.IdentityModel.Tokens.SecurityTokenHandlerCollection { securityTokenHandler },
Scheme = AuthenticationScheme.SchemeAndRealm("Basic", realm)
};
authenticationConfig.AddMapping(authorizationMapping);
var cookieMapping = new AuthenticationOptionMapping
{
Options = AuthenticationOptions.ForCookie("sessionToken"),
TokenHandler = new System.IdentityModel.Tokens.SecurityTokenHandlerCollection { securityTokenHandler },
Scheme = AuthenticationScheme.SchemeOnly(scheme: "Session")
};
authenticationConfig.AddMapping(cookieMapping);
//authenticationConfig.AddBasicAuthentication(_userService.ValidateUser, _userService.GetRolesForUser);
return authenticationConfig;
}
This configuration is then applied like so:
HttpConfiguration config;
var authenticationConfig = CreateAuthenticationConfiguration();
config.MessageHandlers.Add(new AuthenticationHandler(authenticationConfig));
And this is what the cookie looks like in the request header:
Cookie: sessionToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEzNzM2NDA5NjgsImlzcyI6InNlc3Npb24gaXNzdWVyIiwiYXVkIjoiaHR0cDovL3Nlc3Npb24udHQvIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImEiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2F1dGhlbnRpY2F0aW9ubWV0aG9kIjoiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2F1dGhlbnRpY2F0aW9ubWV0aG9kL3Bhc3N3b3JkIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9hdXRoZW50aWNhdGlvbmluc3RhbnQiOiIyMDEzLTA3LTEyVDEzOjU2OjA4LjA5N1oiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmlzdHJhdG9yIiwiSWQiOiIyIn0.UlPeD9HzduQfwHE7NuXi9eMVo40hypi_LBK-f76VYFI; username=a
Any help most appreciated!
So after waiting a few minutes and receiving no replies and desperately needing this functionality I dived into the Thinktecture IdentityModel 4.5 source code to see what was going on and it seems this feature is not actually supported. Not only is it not supported but from the looks of it cookie mapping is not actually implemented.
I forked the repository and made a few small changes to allow for this feature:
https://github.com/ibraheemhlaiyil/Thinktecture.IdentityModel.45
and sent Dominick Baier of Thinktecture this in a pull request:
https://github.com/thinktecture/Thinktecture.IdentityModel.45/pull/95
Cookie usage has it's disadvantages, and it seems Thinktecture are trying to stay away from them as far as possible, however I could not come up with a different solution to my problem - a javascript client web applications that needs to open a new window/tab and maintain the authenticated session in the new window/tab.
If you want to use this feature, you simply set the new CookieName property on the SessionTokenConfiguration object. IdentityModel uses the HeaderName property to determine which header to look up for authentication data. In the same way, if the CookieName property is set this determines which cookie name is looked up for authentication data if no authentication data was found on the header.
In the example below, authentication data is looked for on the cookie named sessionToken if no authentication data is found on the Authorization header.
private static AuthenticationConfiguration CreateAuthenticationConfiguration()
{
var authenticationConfig = new AuthenticationConfiguration
{
ClaimsAuthenticationManager = _authenticationManager,
RequireSsl = false,
SendWwwAuthenticateResponseHeaders = false,
EnableSessionToken = true,
SessionToken = new SessionTokenConfiguration
{
EndpointAddress = "/Authenticate",
DefaultTokenLifetime = new TimeSpan(1, 0, 0),
HeaderName = "Authorization",
CookieName = "sessionToken",
SigningKey = CryptoRandom.CreateRandomKey(32)
}
};
authenticationConfig.AddBasicAuthentication(_userService.ValidateUser, _userService.GetRolesForUser);
return authenticationConfig;
}
As before, this configuration is applied like so during your application start up:
HttpConfiguration config;
var authenticationConfig = CreateAuthenticationConfiguration();
config.MessageHandlers.Add(new AuthenticationHandler(authenticationConfig));
The cookie authentication data has the exact same form as the data sent in the Authorization header, so if sent, the cookie should look like:
Cookie: sessionToken=Session eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEzNzM2NDA5NjgsImlzcyI6InNlc3Npb24gaXNzdWVyIiwiYXVkIjoiaHR0cDovL3Nlc3Npb24udHQvIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImEiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2F1dGhlbnRpY2F0aW9ubWV0aG9kIjoiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2F1dGhlbnRpY2F0aW9ubWV0aG9kL3Bhc3N3b3JkIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9hdXRoZW50aWNhdGlvbmluc3RhbnQiOiIyMDEzLTA3LTEyVDEzOjU2OjA4LjA5N1oiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmlzdHJhdG9yIiwiSWQiOiIyIn0.UlPeD9HzduQfwHE7NuXi9eMVo40hypi_LBK-f76VYFI
Hope someone finds this of some use!

Resources