IdentityServer "invalid_client" error always returned - c#

I'm trying to use IdentityServer3, but don't know why I'm getting "invalid_client" error always, always no matter what I do.
This is the code I'm using:
//Startup.cs (Auth c# project)
public void Configuration(IAppBuilder app) {
var inMemoryManager = new InMemoryManager();
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(inMemoryManager.GetClients())
.UseInMemoryScopes(inMemoryManager.GetScopes())
.UseInMemoryUsers(inMemoryManager.GetUsers());
var options = new IdentityServerOptions {
Factory = factory,
RequireSsl = false
};
app.UseIdentityServer(options);
}
InMemoryManager helper.
//InMemoryManager.cs
public class InMemoryManager {
public List<InMemoryUser> GetUsers() {
return new List<InMemoryUser> {
new InMemoryUser {
Username = "alice",
Password = "password",
Subject = "2",
Claims = new [] {
new Claim("User name", "Alice")
}
}
};
}
public IEnumerable<Scope> GetScopes() {
return new[] {
new Scope {
Name = "api1",
DisplayName = "API 1"
}
};
}
public IEnumerable<Client> GetClients() {
return new[] {
new Client {
ClientName = "Silicon on behalf of Carbon Client",
ClientId = "carbon",
Enabled = true,
//AccessTokenType = AccessTokenType.Reference,
Flow = Flows.ResourceOwner,
ClientSecrets = new List<Secret> {
new Secret("secret".Sha256())
},
AllowedScopes = new List<string> {
"api1"
}
}
};
}
}
This is the result I always get.
I'm using postman to try the Auth Server, but I always get that error. I've read another solutions but none seeme to works, I don't know what else to try.
Cheers.

Just add the client_secret: secret in your Body. It will work!

Late answer, but for me this happened following the IdentityServer 4 tutorial when trying to log in with a username and password. I used the code from the first tutorial (using client credentials), and modified the client to use passwords. Afterwards, I kept getting this error.
To fix it, in the IdentityServer project, config.cs, in the GetClients method, set AllowedGrantTypes to GrantTypes.ResourceOwnerPassword, and change ClientId from client to ro.client (or whatever the client name is that you use in the Client project's program.cs).

Your request shoud be as follows:
Authorisation header with clientId/clientSecret. carbon/secret in Your case.
In Body. username/password shoud be alice/password in Your case. If Your don't need to refresh tokens, You might exclude offline_access scope from request.

Related

How to use Pulumi Output<string> as a string in .NET

I have a basic Pulumi build for keycloak where I set up a realm, create a scope, create a client, and update teh scopes for my client.
class RealmBuild : Stack
{
public RealmBuild()
{
var realm = new Realm("ExampleRealm-realm", new RealmArgs
{
RealmName = "ExampleRealm"
});
var recipemanagementScope = ScopeFactory.CreateScope(realm.Id, "recipe_management");
var recipeManagementPostmanMachineClient = ClientFactory.CreateClientCredentialsFlowClient(realm.Id,
"recipe_management.postman.machine",
"974d6f71-d41b-4601-9a7a-a33084484682",
"RecipeManagement Postman Machine",
"https://oauth.pstmn.io");
recipeManagementPostmanMachineClient.ExtendDefaultScopes(recipemanagementScope.Name);
}
}
public static class ClientExtensions
{
public static void ExtendDefaultScopes(this Client client, params Output<string>[] scopeNames)
{
var defaultScopeName = $"default-scopes-for-{client.Name.Apply(x => x)}";
var defaultScopes = new ClientDefaultScopes(defaultScopeName, new ClientDefaultScopesArgs()
{
RealmId = client.RealmId,
ClientId = client.Id,
DefaultScopes =
{
"openid",
"profile",
"email",
"roles",
"web-origins",
scopeNames,
},
});
}
}
public class ClientFactory
{
public static Client CreateClientCredentialsFlowClient(Output<string> realmId,
string clientId,
string clientSecret,
string clientName,
string baseUrl)
{
return new Client($"{clientName.ToLower()}-client", new ClientArgs()
{
RealmId = realmId,
ClientId = clientId,
Name = clientName,
StandardFlowEnabled = false,
Enabled = true,
ServiceAccountsEnabled = true,
AccessType = "CONFIDENTIAL",
BaseUrl = baseUrl,
AdminUrl = baseUrl,
ClientSecret = clientSecret,
BackchannelLogoutSessionRequired = true,
BackchannelLogoutUrl = baseUrl
});
}
}
The problem is, I am getting this error around my scopes:
Diagnostics:
keycloak:openid:ClientDefaultScopes (default-scopes-for-Calling [ToString] on an [Output<T>] is not supported.
To get the value of an Output<T> as an Output<string> consider:
1. o.Apply(v => $"prefix{v}suffix")
2. Output.Format($"prefix{hostname}suffix");
See https://pulumi.io/help/outputs for more details.
This function may throw in a future version of Pulumi.):
error: Duplicate resource URN 'urn:pulumi:dev::KeycloakPulumiStack::keycloak:openid/clientDefaultScopes:ClientDefaultScopes::default-scopes-for-Calling [ToString] on an [Output<T>] is not supported.
To get the value of an Output<T> as an Output<string> consider:
1. o.Apply(v => $"prefix{v}suffix")
2. Output.Format($"prefix{hostname}suffix");
See https://pulumi.io/help/outputs for more details.
This function may throw in a future version of Pulumi.'; try giving it a unique name
I tried something like this as well var defaultScopeName = Output.Format($"default-scopes-for-{client.Name}");, but I can't pass that into the name for ClientDefaultScopes
I did look at the docs to see if anything stuck out as an issue, but I'm clearly missing something.
Rule number 1 with Pulumi outputs: Anything you return from an apply() will still be an Output, even if it looks like it should be a string.
In other words, on this line of code:
var defaultScopeName = $"default-scopes-for-{client.Name.Apply(x => x)}";
defaultScopeName is Output<string>.
However, the x variable in the lambda is in fact a string rather than an output.
The other item to note is that the name of a resource (so the first argument) cannot be an Output. So in your code:
var defaultScopeName = $"default-scopes-for-{client.Name.Apply(x => x)}";
var defaultScopes = new ClientDefaultScopes(defaultScopeName, new ClientDefaultScopesArgs()
{
RealmId = client.RealmId,
ClientId = client.Id,
DefaultScopes =
{
"openid",
"profile",
"email",
"roles",
"web-origins",
scopeNames,
},
});
because defaultScopeName is an Output, this won't work.
You could create the resource inside of the apply():
var defaultScopea = $"default-scopes-for-{client.Name.Apply(x =>
return new ClientDefaultScopes(x, new ClientDefaultScopesArgs()
{
RealmId = client.RealmId,
ClientId = client.Id,
DefaultScopes =
{
"openid",
"profile",
"email",
"roles",
"web-origins",
scopeNames,
},
});
)}";
however, this may mean that the resource won't appear in any previews (see the note in the Apply section of the Inputs and Outputs page in the Pulumi docs).
So what's the answer here? it looks like you're setting the ClientName to be a string value earlier in the code, so I'd use the same variable that you're setting there.
You can't mix and match string and Output<string> values. Instead, you need to transform any output and append your static list to the list of resolved values:
var defaultScopeName = Output.Format($"default-scopes-for-{client.Name}");
var defaultScopes = new ClientDefaultScopes("some-scope-name", new ClientDefaultScopesArgs()
{
RealmId = client.RealmId,
ClientId = client.Id,
DefaultScopes = Output.All(scopeNames).Apply(names =>
new[] { "openid", "profile", "email", "roles", "web-origins", }
.Concat(names)),
});
Note that Output.Format is used for string formatting, Output.All is used to convert to Output<string[]> and .Apply is used to transform the array. You can learn more in Inputs and Outputs.
Currently, Pulumi only supports string types for the name of a resource.
Since
var defaultScopeName = $"default-scopes-for-{client.Name.Apply(x => x)}";
is using an output of a resource, defaultScopeName is type Output<string> and can't be used for the resource name in the line,
var defaultScopes = new ClientDefaultScopes(defaultScopeName, new ClientDefaultScopesArgs()
If I'm reading the code correctly, you specify clientName and use it to set client.Name. So, I would just pass in clientName and use that instead of client.Name. And, that should work since it's a basic type all the way through.

Calling Microsoft Graph API to create an event, What am I doing wrong?

I've been trying to call Microsoft Graph API for creating events, however I've not been able to do it.
Context: I have a Web MVC application (C#) already in production, with the "common" authentication method, reading a database of users. Recently the customer asked me the possibility to create Microsoft Teams Meetings from the application and also those created meetings have to be scheduled in the Microsoft Teams Calendar with the "Join" button to enter the meeting.
I already configured the API permissions, client secret and used the other properties like tenant, user id, etc from the Azure Portal, I'm sharing a screenshot of my configuration. I'm doing the "Get access on behalf of a user" process.
API Permissions:
Permissions image
Taking the example of the authorize endpoint from the docs, of course I'm replacing the values with my own info
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=11111111-1111-1111-1111-111111111111
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=query
&scope=offline_access%20user.read%20mail.read
&state=12345
Here is my code to Receive the code once the user authorizes the permissions, I'm just storing the value in a static class for testing
public ActionResult ReceiveCode(string code)
{
AuthenticationConfig.Code = code;
//this.Code = code;
return RedirectToAction("Index");
}
Once I got the Auth code, I'm using it to create the event with the generated token, also I already verified that the token contains the permissions given in the Azure Portal.
This is the input for the /events endpoint
var json = JsonConvert.SerializeObject(new
{
subject = "Let's go for lunch",
body = new
{
contentType = "HTML",
content = "Does noon work for you?"
},
start = new
{
dateTime = "2017-04-15T12:00:00",
timeZone = "Pacific Standard Time",
},
end = new
{
dateTime = "2017-04-15T14:00:00",
timeZone = "Pacific Standard Time"
},
location = new
{
displayName = "Harry's Bar",
},
attendees = new List<Attendee>()
{
new Attendee
{
EmailAddress = new EmailAddress
{
Address = "mymail#whatever.com",
Name = "Foo Bar"
},
Type = AttendeeType.Required
}
},
allowNewTimeProposals = true,
isOnlineMeeting = true,
onlineMeetingProvider = "teamsForBusiness",
});
This is the complete method, for the json value, please see the json above. I also tried with the "me" url but it does not work either.
public async Task<ActionResult> OnlineMeeting()
{
try
{
var httpClient = new HttpClient();
var paramsDictionary = new Dictionary<string, string>();
paramsDictionary.Add("client_id",AuthenticationConfig.ClientId);
paramsDictionary.Add("scope", "Calendars.ReadWrite");
paramsDictionary.Add("code", AuthenticationConfig.Code);
paramsDictionary.Add("redirect_uri", "https://localhost:44379/Meeting/Reunion/ReceiveCode");
paramsDictionary.Add("grant_type", "authorization_code");
paramsDictionary.Add("client_secret", AuthenticationConfig.ClientSecret);
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/v2.0/token", "tenant");
var response = await httpClient.PostAsync(url, new FormUrlEncodedContent(paramsDictionary));
if (response.IsSuccessStatusCode)
{
var jsonResponse = await response.Content.ReadAsStringAsync();
var jsonResult = JsonConvert.DeserializeObject(jsonResponse) as JObject;
var accessToken = jsonResult.GetValue("access_token").ToString();
httpClient = new HttpClient();
var json = JsonConvert.SerializeObject(new { });
var defaultRequestHeaders = httpClient.DefaultRequestHeaders;
if (defaultRequestHeaders.Accept == null || !defaultRequestHeaders.Accept.Any(m => m.MediaType == "application/json"))
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var data = new StringContent(json);
response = await httpClient.PostAsync("https://graph.microsoft.com/v1.0/users/{user id}/events", data);
if (response.IsSuccessStatusCode)
{
// Nice
}
else
{
Console.WriteLine($"Failed to call the web API: {response.StatusCode}");
string content = await response.Content.ReadAsStringAsync();
}
}
else
{
Console.WriteLine($"Failed to call the web API: {response.StatusCode}");
string content = await response.Content.ReadAsStringAsync();
}
}
catch (Exception ex)
{
}
return View();
}
I'm able to the get the token, but when trying to create the event returns the next response.
{
"error": {
"code": "ResourceNotFound",
"message": "Resource could not be discovered.",
"innerError": {
"date": "2021-08-31T22:58:18",
"request-id": "c5c07afa-fa89-4948-a9f8-f80ca4cbafc3",
"client-request-id": "c5c07afa-fa89-4948-a9f8-f80ca4cbafc3"
}
}
}
Am I missing something? Maybe the wrong endpoint?
Please, help.
Thanks in advance.

OAuth 2.0 Mailkit "Authentication failed" in MVC, but c# console-app works fine

Because Microsoft ends the support for Basic Authentication access for IMAP in Office 365 I try to update our application to use OAuth 2.0. We use MailKit in a MVC .Net web-application to access an IMAP mailbox, but I get an error saying Authentication failed. However, as a test, I can get it to work in a c# console-application.
The strange thing is:
If I copy the access-token I acquired using the console-application and use it in my web-application I can successfully authenticate and read emails. So that part works.
The authentication itself seems to be successful in the web-application. Our webapp redirects to the Microsoft login-page, MFA works, I see successful audits in Azure A/D and I do get a token in the callback. However, this token gives the Authentication failed error by Mailkit.
In Azure A/D I see some of these errors between the successful audits, but I'm not sure whether they are related or not: Error AADSTS16000 SelectUserAccount - This is an interrupt thrown by Azure AD, which results in UI that allows the user to select from among multiple valid SSO sessions. This error is fairly common and may be returned to the application if prompt=none is specified.
I already verified that the scope for which I acquire a token is the same for both console and web.
The main difference is that I use pca.AcquireTokenInteractive(scopes) in the console application to acquire the token, but I use a webclient call with a call-back in the MVC-controller.
Here is my code (MVC):
public ActionResult Index()
{
string clientID = "[client-id here]";
string clientSecret = "[client-secret here]";
string redirectUri = "[redirectUri here]";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize"),
TokenEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/token"),
ProtocolVersion = ProtocolVersion.V20,
};
List<string> scopes = new List<string>
{
"email",
"offline_access",
"https://outlook.office365.com/IMAP.AccessAsUser.All"
};
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
scopes, new Uri(redirectUri));
return response.AsActionResultMvc5();
}
public async Task<ActionResult> Authorized(string code, string state, string session_state)
{
List<string> scopes = new List<string>
{
"IMAP.AccessAsUser.All",
"User.Read",
"offline_access"
};
HttpClient httpClient = new HttpClient();
var values = new Dictionary<string, string>
{
{ "Host", "https://login.microsoftonline.com" },
{ "Content-Type", "application/x-www-form-urlencoded" },
{ "client_id", "[client-id here]" },
{ "scope", string.Join(" ",scopes) },
{ "code", code },
{ "redirect_uri", [redirectUri here] },
{ "grant_type", "authorization_code" },
{ "client_secret", "[client-secret here]" },
{ "state", state },
};
var content = new FormUrlEncodedContent(values);
var response = await httpClient.PostAsync("https://login.microsoftonline.com/organizations/oauth2/v2.0/token", content);
var jsonString = await response.Content.ReadAsStringAsync();
var oathToken = JsonConvert.DeserializeObject<OathToken>(jsonString);
var oauth2 = new SaslMechanismOAuth2("[Email here]", oathToken.access_token);
var stringBuilder = new StringBuilder();
using (var client = new ImapClient())
{
try
{
await client.ConnectAsync("outlook.office365.com", 993, SecureSocketOptions.Auto);
await client.AuthenticateAsync(oauth2);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
for (int i = 0; i < inbox.Count; i++)
{
var message = inbox.GetMessage(i);
stringBuilder.AppendLine($"Subject: {message.Subject}");
}
await client.DisconnectAsync(true);
return Content(stringBuilder.ToString());
}
catch (Exception e)
{
return Content(e.Message);
}
}
}
The error Authentication failed occurs at the line
await client.AuthenticateAsync(oauth2);
The problem was the scope "email".
We had to remove that. Exactly why, I don't know. It was no problem when used in the console app. Maybe it had to do with the fact we used pca.AcquireTokenInteractive(scopes) in that.

IdentityServer3 connect/token endpoint always return 401: unauthorized

I am trying to setup IdentityServer3 for my project.
When I run IdentityServer3 on local development machine it works all fine, but when I host it on the shared server I get a 401 error. I am trying to access token using endpoint connect\token. Here is the configuration for identityserver3
IdentityServerOptions identityServerOptions = new IdentityServerOptions
{
SiteName = "Ripple IdentityServer",
SigningCertificate = LoadCertificate(),
AuthenticationOptions = new IdentityServer3.Core.Configuration.AuthenticationOptions
{
EnablePostSignOutAutoRedirect = true,
},
LoggingOptions = new LoggingOptions
{
EnableWebApiDiagnostics = true,
WebApiDiagnosticsIsVerbose = true,
EnableHttpLogging = true,
EnableKatanaLogging = true
},
Factory = factory,
};
The strange thing is I am not getting any logs. I know the logs are working because when I access the connect/authorize endpoint, I can see log information. Here is my client registration
client = new Client
{
ClientId = app.Id,
ClientName = app.Name,
Flow = Flows.ResourceOwner,
AllowedScopes = app.AllowedScopes.Split(';').ToList(),
AllowedCorsOrigins = new List<string> { "*" }
};
if (app.Secret != null && app.Secret != "")
{
client.ClientSecrets = new System.Collections.Generic.List<Secret>();
app.Secret = app.Secret.Replace("{", "").Replace("}", "");
string[] secrets = app.Secret.Split(',');
foreach (var s in secrets)
{
client.ClientSecrets.Add(new Secret(s.Sha256()));
}
}
Here is the client code to get access token
var data = new StringContent(string.Format("grant_type=password&username={0}&password={1}&Domain={2}&scope={3}",
HttpUtility.UrlEncode(username),
HttpUtility.UrlEncode(password),
HttpUtility.UrlEncode(domainId),
HttpUtility.UrlEncode(requiredScope)), Encoding.UTF8, "application/x-www-form-urlencoded");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", applicationId, appSecretKey))));
HttpResponseMessage response = client.PostAsync("connect/token", data).Result;
Without logs, I am totally lost. Where should I look for more information to debug?
Found solution. Shared hosting like godaddy did not support Basic authentication. So request to access token was getting rejected on server level. That was the reason why no log file was not are getting generated.
To work around this problem, I have to implement my own version on ISecretParser. In this implementation i parsed of my own authentication header
e.g. Authentication MyAuth ClientID:ClientSecret
Then register this parser with IdentityServerServiceFactory and it worked like charm.
I hope this solution will help others who are trying to host IdentiyServer3 on shared servers.

Request had insufficient authentication scopes. [403]. Google People API

I have method who creates the contact and sends the request. After call Execute() method, an excepted appears. How to correctly send changes in Google contacts?
private readonly PeopleServiceService _peopleService;
private readonly string[] _scopes = { PeopleServiceService.Scope.Contacts };
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets,
_scopes,
userName,
CancellationToken.None).Result;
_peopleService = new PeopleServiceService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "ApplicationName",
});
var contactToCreate = new Person
{
Names = new List<Name>
{
new Name
{
DisplayName = "John"
}
},
PhoneNumbers = new List<PhoneNumber>
{
new PhoneNumber
{
Value = "+7 777 777 7777"
}
}
};
var request = new PeopleResource.CreateContactRequest(_peopleService, contactToCreate);
request.Execute(); // Exception here
That exception:
enter image description here
insufficient authentication scopes.
Means that you dont currently have the permission to do what you are trying to do.
Method: people.createContact requires the following scope of permissions in order to exicute.
https://www.googleapis.com/auth/contacts
You apear to be using that. So one of two things is happening here.
You have changed the scope in your code and failed to logout and reauthenticate the script in order to get the new permissions.
there is some bug in the api. I have tested it and the API appears to be working.
Double check your code make sure your using that Scope then try and login again.

Categories

Resources