How to use SSO with RingCentral C# SDK? - c#

When using the RingCentral C# Client SDK, how can I use Single Sign-On (SSO) which we require for our Production environment? The SDK is working fine in the Sandbox environment without SSO.
I'm using authorization as follows per the documentation, but this only works for RingCentral password auth, not SSO.
await rc.Authorize("username", "extension", "password");
This is for both the current and older SDKs:
New: https://github.com/ringcentral/ringcentral-csharp-client
Old: https://github.com/ringcentral/ringcentral-csharp

Single Sign-On is only supported via the Authorization Code OAuth 2.0 grant flow which will present the user with an login window with a SSO button that will redirect the user to the SAML Identity Provider (IdP) website for SSO-based auth.
Demo code for how to accomplish this with the two C# SDKs are available here:
https://github.com/ringcentral/ringcentral-demos-oauth/tree/master/csharp-nancy
Here's an excerpt showing the two endpoints for the homepage and OAuth redirect URI:
public DefaultModule()
{
var authorizeUri = rc.AuthorizeUri(Config.Instance.RedirectUrl, MyState);
var template = File.ReadAllText("index.html");
Get["/"] = _ =>
{
var tokenJson = "";
var authData = rc.token;
if (rc.token != null && rc.token.access_token != null)
{
tokenJson = JsonConvert.SerializeObject(rc.token, Formatting.Indented);
}
var page = Engine.Razor.RunCompile(template, "templateKey", null,
new { authorize_uri = authorizeUri, redirect_uri = Config.Instance.RedirectUrl, token_json = tokenJson });
return page;
};
Get["/callback"] = _ =>
{
var authCode = Request.Query.code.Value;
rc.Authorize(authCode, Config.Instance.RedirectUrl);
return ""; // js will close this window and reload parent window
};

Related

OAuth2 Implicit Flow with C# Windows Forms

I'm developing a c# windows forms app that needs to authenticate using the Implicit Flow (The client does not accept another flow). As requirement, I need to open the default system browser to authenticate (so no embedded web view on the application)
I'm trying to use the OidcClient C# and the Samples but I can't get it to work.
The closest I got was using the ConsoleSystemBrowser. But using the code below I get always an UnknownError with empty response.
I can see in the browser the id_token: http://127.0.0.1:54423/auth/signin-oidc#id_token=XXX. How can I read it?
var browser = new SystemBrowser();
var redirectUri = string.Format($"http://127.0.0.1:{browser.Port}/auth/signin-oidc");
var options = new OidcClientOptions
{
Authority = "https://demo.identityserver.io",
ClientId = "implicit",
Scope = "openid profile api",
RedirectUri = redirectUri,
Browser = browser
};
var client = new OidcClient(options);
var state = await client.PrepareLoginAsync(new Dictionary<string, string>()
{
{ OidcConstants.AuthorizeRequest.ResponseType, OidcConstants.ResponseTypes.IdTokenToken}
});
var browserOption = new BrowserOptions(state.StartUrl, redirectUri)
{
Timeout = TimeSpan.FromSeconds(300),
DisplayMode = DisplayMode.Hidden,
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect
};
var result = await browser.InvokeAsync(browserOption, default);
result.ResultType => BrowserResultType.UnknownError
Your application should register a private URL scheme with the networking component of the OS. Then, URLs of the form "x-my-app://xxx" will be forwarded to your application. (And you register the URL with the OAuth IdP so it works as a redirect URL.)
For Windows, it appears that Microsoft calls this "Pluggable Protocols". See
programming-pluggable-protocols
An older doc
A source of code examples for this pattern might be from the github desktop application--it is open source and registers its own scheme with Windows.
It registers the private scheme x-github-client You can see how it's done in the source also see here

Image url in Asp.Net 4.5 Webforms when using google authentication

I have a website in ASP.Net 4.5 (not ASP.Net Core) based on webforms.
I am using using the Google button on Login.aspx page to login the user, that comes with the standard template in Visual Studio 2017 Community.
When I look at the code in Visual Studio 2017, I can see code that gets the authentication info against Google ( i.e. loginInfo variable in code below) in RegisterExternalLogin.aspx.cs, but when I inspect the object User.Identity.Claims after authentication in another page's code-behind (like in About.aspx.cs), then I find that there is no claim for image url of logged in user.
Question
Is it possible to get image url from Google using existing ASP.Net Identity/OWIN authentication in .Net framework 4.5 (not ASP.Net Core)? If yes, then how would one go about getting it?
Code executed after Google authentication in RegisterExternalLogin.aspx.cs
if (!IsPostBack)
{
var manager = new UserManager();
var loginInfo = Context.GetOwinContext().Authentication.GetExternalLoginInfo();
if (loginInfo == null)
{
Response.Redirect("~/Account/Login");
}
var user = manager.Find(loginInfo.Login);
if (user != null)
{
IdentityHelper.SignIn(manager, user, isPersistent: false);
IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
}
After some research, I found how to get the profile image url using standard ASP.Net Identity/OWIN in a webforms ASP.Net app.
It must be noted that the response from google after successful authentication already contains the profile image url, but since its not being extracted from the response and made available by asp. net identity/owin framework code, it appears as if the image url is not there . So, all we need to do is extract this pic url and put it into asp.net claims object. (no need to make a special call to google for profile image url)
You need to make following 2 changes to the standard code that's emitted by the Visual Studio template.
In Startup.Auth.cs add custom code that executes when Google authenticates completes its operation and returns its response. This custom code is within the OnAuthenticated function. We will access the profile image url, which can be found by going through children of context.User object, and add a claim for that to Google's context.Identity.
In Startup.Auth.cs
var options = new GoogleOAuth2AuthenticationOptions()
{
ClientId = "someValue1",
ClientSecret = "someValue2",
//ADD CODE AS BELOW in addition to above 2 lines
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = (context) =>
{
//following line will add a new claim for profile image url
context.Identity.AddClaim(new Claim("picUrl", ((Newtonsoft.Json.Linq.JValue)(context.User.SelectToken("image").SelectToken("url"))).Value.ToString()));
return Task.FromResult(0);
}
}
};
When user is being signed in using the standard code's IdentityHelper.SignIn method, then we need to transfer the Google Identity Claim for image to ASP.Net Identity Claim. After this, we can access the image url in code-behind of any aspx page as shown in code-snippet at end of this answer.
In IdentityModels.cs
public static void SignIn(UserManager manager, ApplicationUser user, bool isPersistent)
{
IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
//CUSTOM CODE Start
//get image url claim from Google Identity
if (authenticationManager.GetExternalLoginInfo().Login.LoginProvider == "Google")
{
var externalIdentity = authenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var picClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("picUrl"));
var picUrl = picClaim.Value;
//add a claim for profile pic url to ASP.Net Identity
identity.AddClaim(new System.Security.Claims.Claim("picUrl", picUrl));
}
//CUSTOM CODE end
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent , ExpiresUtc = DateTime.UtcNow.AddMinutes(5), RedirectUri="http://www,appsprocure.com" }, identity);
}
Now, whenever you want to use the profile image url, simply use code like below.
How to use image url claim in code-behind of pages
var ctx = Request.GetOwinContext();
ClaimsPrincipal user = ctx.Authentication.User;
IEnumerable<Claim> claims = user.Claims;
var pictureClaim = claims.FirstOrDefault(c => c.Type == "picUrl");
if (pictureClaim != null)
{
Response.Write(string.Format("<img src='{0}' alt='profileImageMissing' />", pictureClaim.Value));
}

How to implement refresh token workflow into OAUTH workflow in MVC C# app?

I am new to OAUTH. I have been working on implementing OAUTH into my MVC c# application to access ping federate. After much research, and failed attempt at using the ping federate nuget, I came across this link that finally gave some clarity to the full process with a coding example. I have came across much generic examples of the endpoints i need to access but never a full workflow coding example. After implementing that code with some changes and was successful at signing in the ping user into my MVC app, I started doing more research about the refresh token. Questions...
Q. I know how to access a a refresh token, meaning I know which endpoint used to refresh the access token after I have authenticated the user in ping federate. But what is the refresh token used for? Is it used to extend my application's session once it ends? Or it used for if the user signs out of my application then they click the 'Sign in with Ping Federate' link on the login and not have them authenticate again as long as the refresh token is still valid?
Q. And if the refresh token is used for when after a user authenticates the first time, and I save the refresh token in the db and then user signs back using that 'Sign in with Ping Federate' link on my login back how can I know what user that is to lookup the refresh token in the db to give them access to my site without re-authenticating them with ping federate? Since when they come to that link 'Sign in with Ping Federate' I do not know who they are?
This is the below code that I am using, from user MatthiasRamp in the link i provided...I want to add my refresh token logic with the below code.
public async Task<ActionResult> Login(string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl) && Request.UrlReferrer != null)
returnUrl = Server.UrlEncode(Request.UrlReferrer.PathAndQuery);
if (Url.IsLocalUrl(returnUrl) && !string.IsNullOrEmpty(returnUrl))
_returnUrl = returnUrl;
//callback function
_redirectUrl = Url.Action("AuthorizationCodeCallback", "ExternalLogin", null, Request.Url.Scheme);
Dictionary<string, string> authorizeArgs = null;
authorizeArgs = new Dictionary<string, string>
{
{"client_id", "0123456789"}
,{"response_type", "code"}
,{"scope", "read"}
,{"redirect_uri", _redirectUrl}
// optional: state
};
var content = new FormUrlEncodedContent(authorizeArgs);
var contentAsString = await content.ReadAsStringAsync();
return Redirect("http://localhost:64426/oauth/authorize?" + contentAsString);}
public async Task<ActionResult> AuthorizationCodeCallback()
{
// received authorization code from authorization server
string[] codes = Request.Params.GetValues("code");
var authorizationCode = "";
if (codes.Length > 0)
authorizationCode = codes[0];
// exchange authorization code at authorization server for an access and refresh token
Dictionary<string, string> post = null;
post = new Dictionary<string, string>
{
{"client_id", "0123456789"}
,{"client_secret", "ClientSecret"}
,{"grant_type", "authorization_code"}
,{"code", authorizationCode}
,{"redirect_uri", _redirectUrl}
};
var client = new HttpClient();
var postContent = new FormUrlEncodedContent(post);
var response = await client.PostAsync("http://localhost:64426/token", postContent);
var content = await response.Content.ReadAsStringAsync();
// received tokens from authorization server
var json = JObject.Parse(content);
_accessToken = json["access_token"].ToString();
_authorizationScheme = json["token_type"].ToString();
_expiresIn = json["expires_in"].ToString();
if (json["refresh_token"] != null)
_refreshToken = json["refresh_token"].ToString();
//SignIn with Token, SignOut and create new identity for SignIn
Request.Headers.Add("Authorization", _authorizationScheme + " " + _accessToken);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
ctx.Authentication.SignOut(DefaultAuthenticationTypes.ExternalBearer);
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);
var ctxUser = ctx.Authentication.User;
var user = Request.RequestContext.HttpContext.User;
//redirect back to the view which required authentication
string decodedUrl = "";
if (!string.IsNullOrEmpty(_returnUrl))
decodedUrl = Server.UrlDecode(_returnUrl);
if (Url.IsLocalUrl(decodedUrl))
return Redirect(decodedUrl);
else
return RedirectToAction("Index", "Home");
}

Azure AD federated logout not redirecting to client application

I am using Identity Server 3 for a central authentication server to a .Net MVC web application I am building.
I have configured the authentication server to use the Open ID Connect identity provider in order to allow users to authenticate against a multi-tenant Azure Active Directory account, using the Hybrid flow.
Currently, sign in works as expected with my client application redirecting to the authentication server which in turn redirects to Microsoft for login before returning back to my client application with a correctly populated Access Token.
However, when I try to logout I am redirected to Microsoft correctly, but the page stops when it arrives back at the authentication server, rather than continuing back to my client application.
I believe I have setup the post logout redirect correctly as outlined here and I think all of my settings are ok.
When I pull the Identity Server 3 code down and debug it, it is correctly setting the signOutMessageId onto the query string, but hits the following error inside the UseAutofacMiddleware method when it is trying to redirect to my mapped signoutcallback location:
Exception thrown: 'System.InvalidOperationException' in mscorlib.dll
Additional information: Headers already sent
My Authentication Server setup:
app.Map("identity", idsrvApp => {
var idSvrFactory = new IdentityServerServiceFactory();
var options = new IdentityServerOptions
{
SiteName = "Site Name",
SigningCertificate = <Certificate>,
Factory = idSvrFactory,
AuthenticationOptions = new AuthenticationOptions
{
IdentityProviders = ConfigureIdentityProviders,
EnablePostSignOutAutoRedirect = true,
PostSignOutAutoRedirectDelay = 3
}
};
idsrvApp.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
idsrvApp.UseIdentityServer(options);
idsrvApp.Map("/signoutcallback", cb => {
cb.Run(async ctx => {
var state = ctx.Request.Cookies["state"];
ctx.Response.Cookies.Append("state", ".", new Microsoft.Owin.CookieOptions { Expires = DateTime.UtcNow.AddYears(-1) });
await ctx.Environment.RenderLoggedOutViewAsync(state);
});
});
});
My Open Id Connect setup to connect to Azure AD:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "aad",
SignInAsAuthenticationType = signInAsType,
Authority = "https://login.microsoftonline.com/common/",
ClientId = <Client ID>,
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters
{
AuthenticationType = Constants.ExternalAuthenticationType,
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(<Client ID>, <Client Secret>);
string tenantId = context.AuthenticationTicket.Identity.FindFirst("tid").Value;
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}");
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(<Identity Server URI>/aad/"), credential, "https://graph.windows.net");
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/aad/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl + "/signoutcallback";
if (context.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest)
{
var signOutMessageId = context.OwinContext.Environment.GetSignOutMessageId();
if (signOutMessageId != null)
{
context.OwinContext.Response.Cookies.Append("state", signOutMessageId);
}
}
return Task.FromResult(0);
}
});
I cannot find any information about the cause of or solution to this problem. How do I configure this to correctly redirect back to my client application?
Edit:
Related discussion on GitHub: https://github.com/IdentityServer/IdentityServer3/issues/2657
I have also tried this with the latest version of Identity Server on MyGet (v2.4.1-build00452) with the same problem.
I have also created a repository that reproduces the issue for me here: https://github.com/Steve887/IdentityServer-Azure/
My Azure AD setup:
I believe you were experiencing a bug that is fixed in 2.5 (not yet released as of today): https://github.com/IdentityServer/IdentityServer3/issues/2678
Using current source from Git, I still see this problem. It appears to me that AuthenticationController.Logout is hit twice during the logout. Once prior to the external provider's logout page is displayed, and once after. The initial call Queues and clears the signout cookie so that the second time it is not available when rendering the logout page.

Custom Authentication on Asp.Net 4.5 with WIF

I have an application set up with Azure ACS and .net 4.5 using claims. My application uses dropbox also. I was wondering if i could let users identify them self with dropbox alone.
I get a token from dropbox when the user logs in with dropbox and a unique id. Where in the .net pipe do i tell it that i have authenticated a user, such the principals are set on the next request also.
To make the example simple, lets say i have a form with two inputs. name,pass. If the name is 1234 and pass is 1234. then i would like to tell the asp.net pipeline that the user is authenticated. Is this possible? or do i need to create custom token handlers an such to integrate it into WIF?
Update
I found this: I would like comments on the solution, if there are security concerns i should be aware off.
var sam = FederatedAuthentication.SessionAuthenticationModule;
if (sam != null)
{
var cp = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> {new Claim("Provider","Dropbox")}, "OAuth"));
var transformer = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
if (transformer != null)
{
cp = transformer.Authenticate(String.Empty, cp);
}
var token = new SessionSecurityToken(cp);
sam.WriteSessionTokenToCookie(token);
}
All code:
public HttpResponseMessage get_reply_from_dropbox(string reply_from)
{
var response = this.Request.CreateResponse(HttpStatusCode.Redirect);
var q = this.Request.GetQueryNameValuePairs();
var uid = q.FirstOrDefault(k => k.Key == "uid");
if (!string.IsNullOrEmpty(uid.Value))
{
var sam = FederatedAuthentication.SessionAuthenticationModule;
if (sam != null)
{
var cp = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> {new Claim("Provider","Dropbox")}, "OAuth"));
var transformer = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
if (transformer != null)
{
cp = transformer.Authenticate(String.Empty, cp);
}
var token = new SessionSecurityToken(cp);
sam.WriteSessionTokenToCookie(token);
}
}
response.Headers.Location = new Uri(reply_from);
return response;
}
public async Task<string> get_request_token_url(string reply_to)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("OAuth",
string.Format("oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\", oauth_consumer_key=\"{0}\", oauth_signature=\"{1}&\"",
"<dropboxkey>","<dropboxsecret>"));
var data = await client.GetStringAsync("https://api.dropbox.com/1/oauth/request_token");
var pars = data.Split('&').ToDictionary(k=>k.Substring(0,k.IndexOf('=')),v=>v.Substring(v.IndexOf('=')+1));
return "https://www.dropbox.com/1/oauth/authorize?oauth_token=" + pars["oauth_token"]
+ "&oauth_callback=<MYSITE>/api/dropbox/get_reply_from_dropbox?reply_from=" + reply_to;
}
It works by the user request the authentication url, when the user authenticates my app it returns to get_reply_from_dropbox and logs in the user.
I offcause needs to handle some other stuff also, like what if the request do not come from dropbox.
I did this for my site using WIF 3.5 (not exactly the same) but it did use ACS+forms auth+OAuth all together, basically it uses form auth (which you can control completely) or use ACS/OAuth and link the accounts together or just use ACS/OAuth by itself.
You will have to handle logging off differently though.
http://garvincasimir.wordpress.com/2012/04/05/tutorial-mvc-application-using-azure-acs-and-forms-authentication-part-1/
DropBox uses OAuth, so I would go that route and then if you want to "link the accounts" create a user/password for forms auth linked to the DropBox Oauth account. The user doesn't necessarily have to know what auth conventions are being used. ASP.NET MVC 4 has the OAuth/forms auth built in the default project.

Categories