OAuth2 Implicit Flow with C# Windows Forms - c#

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

Related

Authorize Google analytics data from a web application

I am trying to authorize an ASP.NET Core 6 MVC web app to Google analytics data API.
[GoogleScopedAuthorize("https://www.googleapis.com/auth/analytics.readonly")]
public async Task<IActionResult> Index([FromServices] IGoogleAuthProvider auth)
{
var cred = await auth.GetCredentialAsync();
var client = await BetaAnalyticsDataClient.CreateAsync(CancellationToken.None);
var request = new RunReportRequest
{
Property = "properties/" + XXXXX,
Dimensions = {new Dimension {Name = "date"},},
Metrics = {new Metric {Name = "totalUsers"},new Metric {Name = "newUsers"}},
DateRanges = {new DateRange {StartDate = "2021-04-01", EndDate = "today"},},
};
var response = await client.RunReportAsync(request);
}
The authorization goes though as would be expected; I am getting an access token back.
I cant seem to figure out how to apply the credentials to the BetaAnalyticsDataClient.
When I run it without applying it to the BetaAnalyticsDataClient, I get the following error:
InvalidOperationException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
I am not currently using GOOGLE_APPLICATION_CREDENTIALS as it is configured in programs.cs. I don't see the need to have client id and secret configured in program.cs plus having an added env var.
Why isn't it just picking up the authorization already supplied with the controller runs?
builder.Services
.AddAuthentication(o =>
{
// This forces challenge results to be handled by Google OpenID Handler, so there's no
// need to add an AccountController that emits challenges for Login.
o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// This forces forbid results to be handled by Google OpenID Handler, which checks if
// extra scopes are required and does automatic incremental auth.
o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// Default scheme that will handle everything else.
// Once a user is authenticated, the OAuth2 token info is stored in cookies.
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogleOpenIdConnect(options =>
{
options.ClientId = builder.Configuration["Google:ClientId"];
options.ClientSecret = builder.Configuration["Google:ClientSecret"];
});
Is there an alternate method for authorizing with a web app that I have not been able to find. I did do some dinging in the source code I can't seem to find a method to apply this.
After quite a bit of digging i managed to find that it was possible to create my own client builder and apply the credentials there.
var clientBuilder = new BetaAnalyticsDataClientBuilder()
{
Credential = await auth.GetCredentialAsync()
};
var client = await clientBuilder.BuildAsync();
Hope this helps someone else.

Windows Authentication with IdentityServer4 on IIS

I am trying to use Windows Authentication credentials to connect with my native (Winforms, console app) client to Identity Server hosted on IIS. The point is for user to be authenticated by AD and with those credentials get the right claims and roles from the Identity Server (which is run through commercial https://commercial.abp.io/ platform).
EDIT:
I found out it is not client related issue since i cannot use my External login (Windows credentials) even directly on hosted site.
The thing worked locally while hosted by IISExpress, then i published it to IIS and enabled the Anonymous and Windows Authentication in the IIS settings and here is where problems began.
When i run it and click the External Login (Windows Credentials) button i usually get a redirect to https://myserver/Error?httpStatusCode=401
and i get prompt for my windows credentials (which even if i insert correctly, just repeat prompt again).
From time to time i get logged in with my Windows credentials (which is the goal). Login with username and password works fine.
I saw the similar issue mentioned by someone here:
https://github.com/IdentityServer/IdentityServer4/issues/4937 without any solution\answer.
My client is basically the sample NativeConsolePKCEClient from this https://github.com/damienbod/AspNetCoreWindowsAuth
static string _authority = "https://myserver/";
string redirectUri = "https://127.0.0.1:45656";
var options = new OidcClientOptions
{
Authority = _authority,
ClientId = "native.code",
ClientSecret = "secret",
RedirectUri = redirectUri,
Scope = "openid profile",
FilterClaims = false,
Browser = browser,
Flow = OidcClientOptions.AuthenticationFlow.AuthorizationCode,
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,
LoadProfile = true
};
_oidcClient = new OidcClient(options);
var result = await _oidcClient.LoginAsync();
and on server side the startup configuration services:
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.Configure<IISOptions>(iis => // IISOptions
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
context.Services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); ;
options.Audience = "ABPIdentityServer";
});
}
Here is the ProcessWindowsLoginAsync challenge method:
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
// see if windows auth has already been requested and succeeded
var result = await HttpContext.AuthenticateAsync(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme);
if (result?.Principal is WindowsPrincipal wp)
{
// we will issue the external cookie and then redirect the
// user back to the external callback, in essence, tresting windows
// auth the same as any other external authentication mechanism
var props = new AuthenticationProperties()
{
RedirectUri = "./ExternalLoginCallback",
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme },
}
};
var id = new ClaimsIdentity(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
// add the groups as claims -- be careful if the number of groups is too large
{
var wi = (WindowsIdentity)wp.Identity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(IdentityConstants.ExternalScheme, new ClaimsPrincipal(id), props);
return Redirect(props.RedirectUri);
}
else
{
// trigger windows auth
// since windows auth don't support the redirect uri,
// this URL is re-triggered when we call challenge
return Challenge("Windows");
}
}
I am suspecting that this piece of code when calling Challenge somehow returns up redirecting to error page, but i am not sure and i do now why.
So what am i missing? Is it even possible to run both Windows and Anonymous authentication on IIS?
Here i also found similar issue:
identity server 4 windows authentication
but the presented answers did not help me.
I strongly suspect that it's not the client issue it's the token provider's issue (Not the ID4 library but one where you have installed the ID4 library).
I believe that you have added the below code in the AccountController->Login action but make sure that you have added a success check in it, if you miss that then your app will go infinite loop.
[HttpGet] public async Task<IActionResult> Login(string returnUrl)
{
if(loginViewModel.ExternalLoginScheme == "Windows")
{
var authenticationResult = await HttpContext.AuthenticateAsync("Windows").ConfigureAwait(false);
if (authenticationResult.Succeeded && authenticationResult?.Principal is WindowsPrincipal windowsPrinciple)
{
// Add your custom code here
var authProps = new AuthenticationProperties()
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", "Windows"},
}
};
await HttpContext.SignInAsync();
return Redirect(RedirectUri);
}
else
{
return Challenge("Windows");
}
}
}
I hope this will help you to fix your issue.
Happy Coding!!
Just for anybody who might be interested. I found out what was causing the redirect error.
It is somehow connected with the ABP Suite i used for generating the base application.
there in the ApplicationInitialization there was a middleware called
app.UseErrorPage();
Which when the Windows credentials were challenged took it as an Error and redirected to https://myserver/Error?httpStatusCode=401.
I am not sure how this middleware works and why sometimes login worked, but removing this part solved my issue.
I hope this helps somebody, somehow, sometime..

How to use SSO with RingCentral C# SDK?

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

How to use Google Apis using existing Access Token in Xamarin?

I implemented login process successfully in my Xamarin forms app using Xamarin.auth. now I want to connect to Google APIs and upload AppData. here is the Code I tried,
I tread to fetch the GoogleCredential using token and providing this Credential to Google API but it failed.
var store = AccountStore.Create();
var SavedAccount = store.FindAccountsForService(GoogleDriveBackup.Auth.Constants.AppName).FirstOrDefault();
GoogleCredential credential2 = GoogleCredential.FromAccessToken(SavedAccount.Properties["access_token"]);
var driveService = new Google.Apis.Drive.v3.DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential2,
ApplicationName = "myApp",
});
FilesResource.CreateMediaUpload request;
var filePath = Path.Combine(path, filename);
using (var stream = new System.IO.FileStream(filePath,
System.IO.FileMode.Open))
{
request = driveService.Files.Create(
fileMetadata, stream, "application/json");
request.Fields = "id";
request.Upload();
}
var file = request.ResponseBody;
request.ResponseBody is always null. I thought that it has something to do with credentials.
I tried using
var store = AccountStore.Create();
var SavedAccount = store.FindAccountsForService(GoogleDriveBackup.Auth.Constants.AppName).FirstOrDefault();
var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "xxxx-xxx.apps.googleusercontent.com",
ClientSecret = "xxxxxxxx"
}
});
Google.Apis.Auth.OAuth2.Responses.TokenResponse responseToken = new Google.Apis.Auth.OAuth2.Responses.TokenResponse()
{
AccessToken = SavedAccount.Properties["access_token"],
ExpiresInSeconds = Convert.ToInt64(SavedAccount.Properties["expires_in"]),
RefreshToken = SavedAccount.Properties["refresh_token"],
Scope = DriveService.Scope.DriveAppdata,
TokenType = SavedAccount.Properties["token_type"],
};
var token= SavedAccount.Properties["access_token"];
var credential = new UserCredential(flow, "", responseToken);
But above case requires Client Secret which I don't have as I created "Android App" in the google console and signed in using on ClientId. So I read somewhere that I should create "Others" in the google console and use ClientId and Client Secret from there which makes not much sense to me because I am logged in with different client id's. Anyway, I tried that also but the response was null.
So what is the deal here? How can achieve my goal?
The google .net client library doesn't support xamarin. I am actually surprised you got it working this far. The main issue you are going to have is the authentication as you have already noticed the credential type for the .net client library is going to be either browser, native or api key. The mobile (Android Ios) clients arnt going to work as you dont have a secret the method of authentication is different and the client library doesn't have ability to do this.
The only suggestion i would have would be to work out Oauth2 authentication with xamarin on your own and then build the TokenResponse as you are doing now. You may then be able to feed that token to the Google .net client library if you can get the dlls into your project.
To my knowlage we have no plans to support xamarinwith the Google .net clinet library in the near future please see 984 840 1167

C# code to pull data from yammer using Avocado rest api

I want to pull data from Yammer via Avocado Api where in all views are created in api, I followed below link however I 'm not able to authenticate and secondly there is not code to pull data from avocado rest api, could you please help me with some code.
here is the api link for reference
https://avocadowiki.azurewebsites.net/wiki/Backend_API_Client_Examples
here is the reports link
https://avocado/
According to your description, I followed this avocado-console-client sample to test it on my side. I could make it work as expected, you could refer to the steps below:
1.Update Microsoft.IdentityModel.Clients.ActiveDirectory to the latest stable version (3.13.8) via NuGet
2.Modify the function authCtx.AcquireToken to authCtx.AcquireTokenAsync as follows:
var authResult = await authCtx.AcquireTokenAsync(avocadoResourceUri, thisConsoleAppClientId, thisConsoleAppUri, new PlatformParameters(PromptBehavior.Auto));
3.Register your native app and modify thisConsoleAppClientId and thisConsoleAppUri
Sign in to the Azure Portal, click "Azure Active Directory" > "App registrations", click Add buttion to add your native app as follows:
For thisConsoleAppClientId(your client app Id), you could find it on Azure Portal:
For thisConsoleAppUri (the return url of your app):
Note: In order to access Avocado API, you need to assign your app with the delegated permission to Avocado. You could follow the steps below:
Select you AD app on Azure, In the Settings blade click "Required permissions" to add a permission and choose "Have full access to the service" option.
Note: You could input Avocado [wsfed enabled] to select the Avocado API.
Upon the configurations, you could test it. Here is my code snippet:
Program.cs
class Program
{
static void Main(string[] args)
{
AvocadoDemo();
Console.WriteLine("press any key to exit...");
Console.ReadKey();
}
static async Task AvocadoDemo()
{
string thisConsoleAppClientId = "c588cf8d-8651-4b37-8d10-49237cf92f8e";
Uri thisConsoleAppUri = new Uri("http://bruceConsoleForAvocado");
string avocadoResourceUri = "https://microsoft.onmicrosoft.com/Avocado";
// Get the access token for Avocado. This will pop up the login dialog and consent page for the first time.
// Then the token will be cached in the FileCache, and AcquireToken will renew the token automatically in the subsequent run.
AuthenticationContext authCtx = new AuthenticationContext("https://login.windows.net/microsoft.onmicrosoft.com", new FileCache());
var authResult = await authCtx.AcquireTokenAsync(avocadoResourceUri, thisConsoleAppClientId, thisConsoleAppUri, new PlatformParameters(PromptBehavior.Auto));
if (!string.IsNullOrEmpty(authResult.AccessToken))
Console.WriteLine("accessToken: " + authResult.AccessToken);
// call Avocado API
var baseAddress = new Uri("https://avocado");
var cookieContainer = new CookieContainer();
// POSTing a new execution will invoke a redirect, so for example purposes we are disabling it here.
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer, AllowAutoRedirect = false })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
// Need to set this cookie for avocado api to work.
cookieContainer.Add(baseAddress, new Cookie("deep_link", "-1"));
// add your access token in the header
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
// Here's a GET example
var avoResultGet = client.GetAsync("/schedules/7215.json").Result;
var strGet = avoResultGet.Content.ReadAsStringAsync().Result;
Console.WriteLine("Avocado API returns: " + strGet);
}
}
}
Result
Since you have been authenticated, you could refer to API Documentation to invoke the specific APIs as you wish.

Categories