AccessToken is null for identity server client - identityserver3

I have following openid options:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
ClientId = "mvcClient",
ClientSecret = "secret",
RedirectUri = "http://localhost:5002/signin-oidc",
PostLogoutRedirectUri = "http://localhost:5002",
ResponseType = "code id_token",
Scope = "openid profile",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var claims_to_exclude = new[]
{
"aud", "iss", "nbf", "exp", "nonce", "iat", "at_hash"
};
var claims_to_keep =
n.AuthenticationTicket.Identity.Claims
.Where(x => false == claims_to_exclude.Contains(x.Type)).ToList();
claims_to_keep.Add(new Claim("id_token", n.ProtocolMessage.IdToken));
if (n.ProtocolMessage.AccessToken != null)
{
claims_to_keep.Add(new Claim("access_token", n.ProtocolMessage.AccessToken));
}
}
}
}
I see n.ProtocolMessage.AccessToken is always null.
I configured client in identity server like this:
new Client()
{
ClientId = "mvcClient",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets = new List<Secret>()
{
new Secret("secret".Sha256())
},
// RequireConsent = false,
// where to redirect to after login
RedirectUris = { "http://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5002" },
AllowedScopes =
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
StandardScopes.Roles.Name,
"API"
}
},
I want to know why n.ProtocolMessage.AccessToken is null and how can i get its value
UPDATE
If I change Client Type to Hybrid like this:
AllowedGrantTypes = GrantTypes.Hybrid,
and ResponseType = "code id_token token:
I get invalid_request error on server
If I try to get access token like this (in notifications):
var client = new TokenClient("http://localhost:5000/connect/token", "mvcClient", "secret");
var response = client.RequestClientCredentialsAsync("testscope").Result;
var accesstoken = response.AccessToken;
claims_to_keep.Add(new Claim("access_token", accesstoken));
The result token has only one scope(i.e testscope) instead of all other scopes defined for that client.

It's null because you're not asking for an access token.
ResponseType = "code id_token" means give the client a "Authoriziation Code" and a "Id token" on the callback. To receive an access token,either
include token in ResponseType as ResponseType = "code id_token token" & update the client flow to Hybrid flow (code + token), since that's what we're now doing.
or
fetch an access token using the /token endpoint using the "Authorization Code" available on the ProtocolMessage.

The access token should not be brought back along with code and id_token.
The right way to get it is through the back channel using client id and client secret.
Add this to the Notifications block:
AuthorizationCodeReceived = async n =>
{
var tokenClient = new TokenClient(n.Options.Authority + "/connect/token", "Client_Id", "secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
else
{
string accessToken = tokenResponse.AccessToken;
//Other logic
}
}

Related

Facebook complains that the app is not secure

When I try to login via Facebook, it throws the following error;
facebook has detected app isn't using a secure connection to transfer information
But I'm pretty sure that it is secured via 'Let's encrypt'.
I have checked Web and Client OAuth login boxes and set the corresponding redirect uris on developer facebook.
On maui side, I am calling the following code piece and AppSettings.BaseUrl is correct, I have checked that;
WebAuthenticatorResult authResult = await WebAuthenticator.Default.AuthenticateAsync(
new WebAuthenticatorOptions()
{
Url = new Uri($"{AppSettings.BaseUrl}account/authentication/{scheme}"),
CallbackUrl = new Uri("tibi://"),
PrefersEphemeralWebBrowserSession = true
});
And on backend side, the following api is requested;
[HttpGet("authentication/{scheme}")]
[AllowAnonymous]
public async Task Get([FromRoute] string scheme)
{
var auth = await Request.HttpContext.AuthenticateAsync(scheme);
if (!auth.Succeeded
|| auth?.Principal == null
|| !auth.Principal.Identities.Any(id => id.IsAuthenticated)
|| string.IsNullOrEmpty(auth.Properties.GetTokenValue("access_token")))
{
// Not authenticated, challenge
await Request.HttpContext.ChallengeAsync(scheme);
}
else
{
var claims = auth.Principal.Identities.FirstOrDefault()?.Claims;
var email = string.Empty;
email = claims?.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Email)?.Value;
// Get parameters to send back to the callback
var qs = new Dictionary<string, string>
{
{ "access_token", auth.Properties.GetTokenValue("access_token") },
{ "refresh_token", auth.Properties.GetTokenValue("refresh_token") ?? string.Empty },
{ "expires_in", (auth.Properties.ExpiresUtc?.ToUnixTimeSeconds() ?? -1).ToString() },
{ "email", email }
};
// Build the result url
var url = callbackScheme + "://#" + string.Join(
"&",
qs.Where(kvp => !string.IsNullOrEmpty(kvp.Value) && kvp.Value != "-1")
.Select(kvp => $"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}"));
// Redirect to final url
Request.Host = HostString.FromUriComponent(AppSettingsProvider.GatewayUrl);
Request.HttpContext.Response.Redirect(url);
}
}
Challenge is invoked successfully and redirects me to the Facebook login but when I sign in, I get the error above.

Log in to a GMail account using OAuth 2.0 error

I use this code to login to gmail using oauth 2.o:
var clientSecrets = new ClientSecrets
{
ClientId = "xxx-xxxlpoimr2lrvo2.apps.googleusercontent.com"
// ClientSecret = "XXX"
};
var codeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
// Cache tokens in ~/.local/share/google-filedatastore/CredentialCacheFolder on Linux/Mac
DataStore = new FileDataStore("CredentialCacheFolder", false),
Scopes = new[] { "https://mail.google.com/" },
ClientSecrets = clientSecrets
});
var codeReceiver = new LocalServerCodeReceiver();
var authCode = new AuthorizationCodeInstalledApp(codeFlow, codeReceiver);
var credential = await authCode.AuthorizeAsync(Chatting.Setings.CSetings.email, CancellationToken.None);
if (authCode.ShouldRequestAuthorizationCode(credential.Token))
await credential.RefreshTokenAsync(CancellationToken.None);
var oauth2 = new SaslMechanismOAuth2(credential.UserId, credential.Token.AccessToken);
I get error:
Monday 2022-05-09 11:14:40 EmailParser OpenPop3 Failed to launch browser with "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&client_id=xxxxxxhlpoimr2lrvo2.apps.googleusercontent.com&redirect_uri=http%3A%2F%2F127.0.0.1%3A45169%2Fauthorize%2F&scope=https%3A%2F%2Fmail.google.com%2F" for authorization. See inner exception for details.
Monday 2022-05-09 11:14:40 EmailParser OpenPop3 Cannot find the specified file
What's the problem?

signalr core (2.1) JWT authentication hub/negotiate 401 Unauthorized

so I have a .net core (2.1) API that uses JWT tokens for authentication. I can login and make authenticated calls successfully.
I am using React (16.6.3) for the client, which getting a JWT code and making authenticated calls to the API works.
I am trying to add signalr hubs to the site. If I do not put an [Authorize] attribute on the hub class. I can connect, send and receive messages (its a basic chathub at the moment).
when I add the [Authorize] attribute to the class, the React app will make an HttpPost to example.com/hubs/chat/negotiate . I would get a 401 status code. the Authorization: Bearer abc..... header would be passed up.
To build the hub in React I use:
const hubConn = new signalR.HubConnectionBuilder()
.withUrl(`${baseUrl}/hubs/chat`, { accessTokenFactory: () => jwt })
.configureLogging(signalR.LogLevel.Information)
.build();
where the jwt variable is the token.
I have some setup for the authentication:
services.AddAuthentication(a =>
{
a.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
a.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = false;
options.Audience = jwtAudience;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtIssuer,
ValidAudience = jwtAudience,
RequireExpirationTime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtKey)),
};
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var authToken = context.Request.Headers["Authorization"].ToString();
var token = !string.IsNullOrEmpty(accessToken) ? accessToken.ToString() : !string.IsNullOrEmpty(authToken) ? authToken.Substring(7) : String.Empty;
var path = context.HttpContext.Request.Path;
// If the request is for our hub...
if (!string.IsNullOrEmpty(token) && path.StartsWithSegments("/hubs"))
{
// Read the token out of the query string
context.Token = token;
}
return Task.CompletedTask;
}
};
});
the OnMessageReceived event does get hit and context.Token does get set to the JWT Token.
I can't figure out what I am doing wrong to be able to make authenticated calls for signalr core.
solution
I updated my code to use 2.2 (not sure if this was actually required).
so I spent some time looking at the source code, and the examples within:
https://github.com/aspnet/AspNetCore
I had a Signalr CORS issue which was solved with:
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetIsOriginAllowed((host) => true) //allow all connections (including Signalr)
);
});
the important part being .SetIsOriginAllowed((host) => true) This allows all connections for both website and signalr cors access.
I had not added
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.NameIdentifier);
});
});
I had only used services.AddAuthentication(a =>
I took the following directly from the samples in github
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken) &&
(context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
{
context.Token = context.Request.Query["access_token"];
}
return Task.CompletedTask;
}
};
Not sure if this was needed in the attribute, but the same used it on its hubs
[Authorize(JwtBearerDefaults.AuthenticationScheme)]
with that I could not get multiple website and console apps to connect and communicate via signalr.
To be used with [Authorize] you need to set the request header. Since web sockets do not support headers, the token is passed with the query string, which you correctly parse. The one thing that's missing is
context.Request.Headers.Add("Authorization", "Bearer " + token);
Or in your case probably context.HttpContext.Request.Headers.Add("Authorization", "Bearer " + token);
Example:
This is how i do it. On the client:
const signalR = new HubConnectionBuilder().withUrl(`${this.hubUrl}?token=${token}`).build();
On the server, in Startup.Configure:
app.Use(async (context, next) => await AuthQueryStringToHeader(context, next));
// ...
app.UseSignalR(r => r.MapHub<SignalRHub>("/hubUrl"));
Implementation of AuthQueryStringToHeader:
private async Task AuthQueryStringToHeader(HttpContext context, Func<Task> next)
{
var qs = context.Request.QueryString;
if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"]) && qs.HasValue)
{
var token = (from pair in qs.Value.TrimStart('?').Split('&')
where pair.StartsWith("token=")
select pair.Substring(6)).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token);
}
}
await next?.Invoke();
}
best method for using authorization for hubs is to force the application add the jwt token from the query string to the context
and its working for me via this method
put this inside your program.cs (dot net 6):
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true
};
o.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (string.IsNullOrEmpty(accessToken) == false)
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
and my hubs are like this its using the main authorize method of asp
[Microsoft.AspNetCore.Authorization.Authorize]
public async Task myhub()
{
//do anything in your hub
}
youtube totorial that helped me to solve this problem

Unsupported Grant Type with CustomGrantValidator with IdentityServer 3

I'm trying to set up our IdentityServer solution to accept a custom Grant Validator. Our API project is accessed by to UIs, one that uses Password authentication (which is working) and now one that will use a 3rd party authentication.
In our API I've set up IdentityServer like so:
Startup.cs
public void Configuration(IAppBuilder app)
{
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
var userService = new IdentityUserService();
factory.UserService = new Registration<IUserService>(resolver => userService);
factory.CustomGrantValidators.Add(
new Registration<ICustomGrantValidator, MyGrantValidator>());
var options = new IdentityServerOptions
{
SiteName = "My App Name",
SigningCertificate = Certificate.Get(),
Factory = factory
};
app.Map("/identity", identityServerApp =>
{
identityServerApp.UseIdentityServer(options);
});
}
MyGrantValidator.cs:
public class MyGrantValidator : ICustomGrantValidator
{
public async Task<CustomGrantValidationResult> ValidateAsync(ValidatedTokenRequest request)
{
// For now I just want a basic response. More logic will come later.
var authResult = new AuthenticateResult(
subject: "1234", // user.AccountId.ToString(),
name: "bob" //context.UserName
);
var grantResult = new CustomGrantValidationResult
{
IsError = authResult.IsError,
Error = authResult.ErrorMessage,
ErrorDescription = authResult.ErrorMessage,
Principal = authResult.User
};
return await Task.FromResult(grantResult);
}
public string GrantType => "myGrantType";
}
In my UI, I setup a client like this:
var owinContext = HttpContext.GetOwinContext();
var token = owinContext.Authentication.User.FindFirst(c => c.Type == "myToken")?.Value;
var tokenId = owinContext.Authentication.User.FindFirst(c => c.Type == ClaimTypes.Sid)?.Value;
var client = new TokenClient(
ConfigurationManager.AppSettings["IdentityServerBaseUrl"] + "/connect/token",
"MyUser",
ConfigurationManager.AppSettings["MyClientSecret"],
AuthenticationStyle.Custom
);
var tokenResponse = client.RequestCustomGrantAsync(
"myGrantType",
"read write",
new Dictionary<string, string>
{
{ "token", token },
{ "tokenId", tokenId }
}
).Result;
return Redirect(returnUrl);
When the Request is triggered, I get: unsupported_grant_type
What am I missing?
You're using a client called "MyUser" (weird name for a client, but ok). Is that client registered as one of the in-memory clients with grant type set to "custom"?

API Gateway Custom Authorizer for Federated Identity

I created a custom authorizer for API Gateway so that i can pass the Facebook token and it will authenticate it using Cognito's Federated identity.
My problem is that the fb token seems to expire so I keep getting 403 errors. I am wondering if my approach is correct. Should I pass the Facebook token as part of the request header to API gateway on every REST API call or so I pass AWS identity id instead. Any feedback is appreciated. Thank you.
var AWS = require('aws-sdk');
var cognitoidentity = new AWS.CognitoIdentity();
exports.handler = (event, context, callback) => {
var params = {
IdentityPoolId: 'us-west-2:xxxxxxxxxxxxxxxxx’, /* required */
AccountId: ‘xxxxxxxxxxxxxxxxx,
Logins: {
'graph.facebook.com': event.authorizationToken //Token given by Facebook
}
};
console.log(event.methodArn);
cognitoidentity.getId(params, function(err, data) {
if (err) {
console.log(err);
callback(null, generatePolicy('user', 'Deny', event.methodArn));
}
else{
console.log("success");
callback(null, generatePolicy('user', 'Allow', event.methodArn));
}
});
};
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17'; // default version
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke'; // default action
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
return authResponse;
}