OData Client App Authentication with Azure App Service Example - odata

Is there an example of a external OData client using AAD to access an Azure App Service acting as a OData server? The identity being provided is of the client app itself registered as a native app in AAD, not the user, so no user authentication interface is needed in the app.

Actually, it's same as regular Web API to set up AD for Odata service. So you can refer to this sample: https://github.com/azure-samples/active-directory-dotnet-daemon
The minor difference is on client side. As the client code is generated by "Odata Client Code Generator", it is not using HTTP Client class. You need to leverage DataServiceContext.SendingRequest Event to add authorization header.
Refer to the code below:
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
private static string OdataServiceId = ConfigurationManager.AppSettings["OdataTestId"];
static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static AuthenticationContext authContext = null;
private static ClientCredential clientCredential = null;
// Get an entire entity set.
static void ListAllProducts(Default.Container container)
{
foreach (var p in container.Products)
{
Console.WriteLine("{0} {1} {2}", p.Name, p.Price, p.Category);
}
}
static void AddProduct(Default.Container container, OdataTest.Models.Product product)
{
container.SendingRequest2 += Container_SendingRequest2;
container.AddToProducts(product);
var serviceResponse = container.SaveChanges();
foreach (var operationResponse in serviceResponse)
{
Console.WriteLine("Response: {0}", operationResponse.StatusCode);
}
}
private static void Container_SendingRequest2(object sender, Microsoft.OData.Client.SendingRequest2EventArgs e)
{
AuthenticationResult result= authContext.AcquireTokenAsync(OdataServiceId, clientCredential).Result;
e.RequestMessage.SetHeader("Authorization", "Bearer " + result.AccessToken);
}
static void Main(string[] args)
{
authContext = new AuthenticationContext(authority);
clientCredential = new ClientCredential(clientId, appKey);
// TODO: Replace with your local URI.
string serviceUri = "http://localhost:59837/";
var container = new Default.Container(new Uri(serviceUri));
var product = new OdataTest.Models.Product()
{
Name = "Yo-yo",
Category = "Toys",
Price = 4.95M
};
AddProduct(container, product);
ListAllProducts(container);
Console.ReadKey();
}
}

Related

How do I connect to Oracle Netsuite using OAuth 2.0 Client Credentials Flow and JWT certificate with .net core

I am trying to use the Netsuite Rest api. Below are the steps I took.
https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_162730264820.html
Created a Integration Record in Netsuite
Create a self signed cert:
openssl req -x509 -newkey rsa:4096 -sha256 -keyout auth-key.pem -out auth-cert.pem -nodes -days 730
Added the auth-cert.pem to the integration in Netsuite
Tried calling the TokenUrl endpoint to get access token
I keep getting Bad Request (Status code 400) when I call GetNetsuiteJwtAccessToken(string signedJWTAssertion) to get access token from TokenUrl.
static void Main(string[] args)
//static string Scope = "rest_webservices";
//static string Aud = "https://<Tenant>-sb1.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token";
//static string TokenUrl = "https://<Tenant>-sb1.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token";
//static string TenantName = "<Tenant>";
//static string ClientId = "<ClientId>";
//static string Issuer = ClientId;
//static string ClientSecret = "<Client Secret>";
//static string AppId = "<AppId>";
//static string Kid = "<Key from the Netsuite for the uploaded Cert">;
{
var jwt= GenerateNetsuiteJWTFromPEMFile("auth-key.pem");
var accessToken = GetNetsuiteJwtAccessToken(signedJWTAssertion: jwt);
}
public static string GenerateNetsuiteJWTFromPEMFile(string PEMFile)
{
var tokenHandler = new JwtSecurityTokenHandler();
var rsaPem = File.ReadAllText(PEMFile);
var privatekey = RSA.Create();
privatekey.ImportFromPem(rsaPem);
var key = new RsaSecurityKey(privatekey);
//key.KeyId = Kid;
var signingCredentials = new SigningCredentials(
key: key,
algorithm: SecurityAlgorithms.RsaSha256
);
//signingCredentials.Key.KeyId = Kid;
var Now = DateTimeOffset.UtcNow;
var Exp = Now.AddMinutes(30).ToUnixTimeSeconds();
var Iat = Now.ToUnixTimeSeconds();
var jwt = new SecurityTokenDescriptor
{
Issuer = Issuer,
Claims = new Dictionary<string, object>()
{
["iss"] = Issuer,
["scope"] = Scope,
["aud"] = Aud,
["exp"] = Exp,
["iat"] = Iat
},
SigningCredentials = signingCredentials
};
var jws = tokenHandler.CreateToken(jwt);
var encoded = new JwtSecurityTokenHandler().WriteToken(jws);
return encoded;
}
public static string GetNetsuiteJwtAccessToken(string signedJWTAssertion)
{
string accessToken;
HttpClient _httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Clear();
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"),
new KeyValuePair<string, string>("assertion", signedJWTAssertion)
};
using (var content = new FormUrlEncodedContent(requestParams))
{
var response = _httpClient.PostAsync(TokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
accessToken = responseContent;
}
return accessToken;
}
I ran into the exact same issue and here's how I resolved it.
The function below actual sends the request:
public async Task GetAccessToken()
{
string tokenBaseUrl = <token endpoint URL>;
string consumerKey = <consumer key/client ID from NetSuite>;
// Don't worry about _configurationService below
string assertion = new JwtToken(_configurationService).GetJwtToken(consumerKey);
var parameters = new Dictionary<string, string>
{
{"grant_type", "client_credentials" },
{"client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" },
{"client_assertion", assertion } // use client_assertion, not assertion, the example provided in the docs uses the former
};
var content = new FormUrlEncodedContent(parameters);
var response = await _httpClient.PostAsync(tokenBaseUrl, content);
}
You can extract the access token from response at the end. I just haven't gotten to that.
Now the magic happens in the function below, which creates the JWT token:
public string GetJwtToken()
{
try
{
// Read the content of a private key PEM file, PKCS8 encoded.
string privateKeyPem = File.ReadAllText(<file path to private key>);
// keep only the payload of the key.
privateKeyPem = privateKeyPem.Replace("-----BEGIN PRIVATE KEY-----", "");
privateKeyPem = privateKeyPem.Replace("-----END PRIVATE KEY-----", "");
// Create the RSA key.
byte[] privateKeyRaw = Convert.FromBase64String(privateKeyPem);
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.ImportPkcs8PrivateKey(new ReadOnlySpan<byte>(privateKeyRaw), out _);
RsaSecurityKey rsaSecurityKey = new RsaSecurityKey(provider);
// Create signature and add to it the certificate ID provided by NetSuite.
var signingCreds = new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256);
signingCreds.Key.KeyId = <certificate ID provided when auth cert uploaded to NetSuite>;
// Get issuing timestamp.
var now = DateTime.UtcNow;
// Create token.
var handler = new JsonWebTokenHandler();
string token = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = <consumer key/client ID>,
Audience = <token endpoint URL>,
Expires = now.AddMinutes(5),
IssuedAt = now,
Claims = new Dictionary<string, object> { { "scope", new[] { "rest_webservices" } } },
SigningCredentials = signingCreds
});
return token;
}
catch (Exception e)
{
throw new <custom exception>("Creating JWT bearer token failed.", e);
}
This returns a status 200, so if it still doesn't work for you, I would double check if you set up all the NetSuite 0Auth 2.0 settings correctly.

OpenIDConnectAuthentication is not working with some users in the Azure Active Directory

I have two AD groups in azure K100User and K100Admin. The below code is working fine however, This code is not working for the users who are part of more than 200 AD groups.
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string aadInstance = EnsureTrailingSlash(ConfigurationManager.AppSettings["ida:AADInstance"]);
private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
private static string authority = aadInstance + tenantId;
private static string k100User = ConfigurationManager.AppSettings["K100User"];
private static string k100Admin = ConfigurationManager.AppSettings["K100Admin"];
public void ConfigureAuth(IAppBuilder app)
{
ClaimsIdentity claimsIdentity1 = ClaimsPrincipal.Current.Identity as ClaimsIdentity;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = (ctx) =>
{
ClaimsIdentity claimsIdentity = ClaimsPrincipal.Current.Identity as ClaimsIdentity;
var claims = ctx.AuthenticationTicket.Identity.FindAll("groups");
var appRoles = new List<Claim>();
foreach (var item in claims)
{
var groupStringValue = item.Value;
if (groupStringValue == k100Admin)
{
appRoles.Add(new Claim(ClaimTypes.Role, "K100Admin", ClaimValueTypes.String));
}
else if (groupStringValue == k100User)
{
appRoles.Add(new Claim(ClaimTypes.Role, "K100User", ClaimValueTypes.String));
}
}
if (appRoles.Count > 0)
{
ctx.AuthenticationTicket.Identity.AddClaim(appRoles[0]);
}
return Task.FromResult(0);
}
}
});
}
Take a look at Groups overage claim:
In your scene, the user is member of more than 200 groups, so you need to check for the claim _claim_names with one of the values being groups. If found, make a call to the endpoint specified in _claim_sources to fetch user’s groups.
You can also choose to directly call Microsoft Graph List memberOf to get the groups.
A similar thread for your reference.

OpenID Connect: How to add custom claims data in the client credential flow

I'm setting up a client credential flow with my identity server to get an access token from a client. I'm able to get the access token with the following code,
Identity server configuration:
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var corsPolicyService = new DefaultCorsPolicyService()
{
AllowAll = true
};
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryUsers(Users.Get());
var options = new IdentityServerOptions
{
Factory = idServerServiceFactory,
SiteName = "Demo",
IssuerUri = IdentityConstants.IssuerUri,
PublicOrigin = IdentityConstants.STSOrigin,
SigningCertificate = LoadCertificate()
};
idsrvApp.UseIdentityServer(options);
});
}
Identity Server - Client configuration:
public static class Clients
{
public static IEnumerable<Client> Get()
{
return new[]
{
new Client
{
ClientId = "ClientSDK",
ClientName = "Client SDK (Client Credentials)",
Flow = Flows.ClientCredentials,
AllowAccessToAllScopes = true,
ClientSecrets = new List<Secret>()
{
new Secret(IdentityConstants.ClientSecret.Sha256())
}
}
};
}
}
MVC Client:
var oAuth2Client = new TokenClient(
IdentityConstants.STSTokenEndpoint,
"ClientSDK",
IdentityConstants.ClientSecret);
var tokenResponse = oAuth2Client.RequestClientCredentialsAsync("MyScope").Result;
return tokenResponse.AccessToken;
I'm able to get the access token(i.e. JWT). Can one please tell me how to add a unique key like (UserId) from my database, when the JWT is created with its claims data when the token is created.
First, you need to create custom attribute "userId" on Azure Portal, and apply it for selected application. Then follow this example,
Update user using Graph API
If you are using built in user flows, then you need to select "userId" for your application.
If you are using custom policy, then following process.
JWT token shows only output claims of Azure AD B2C custom policy. It is a multi steps process to create and update custom policy. Here is link to read more about How to create custom attribute
You should implement custom user store for validating user and adding claims from database. Change startup code like below, Userrepository class represents database communication to authenticate user and get claims from database:
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.AddCustomUserStore();
Add below classes and change according to your requirement:
public static class CustomIdentityServerBuilderExtensions
{
public static IIdentityServerBuilder AddCustomUserStore(this IIdentityServerBuilder builder)
{
builder.AddProfileService<UserProfileService>();
builder.AddResourceOwnerValidator<UserResourceOwnerPasswordValidator>();
return builder;
}
}
public class UserProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
UserRepository userRepository=new UserRepository();
var user = userRepository.GetUserById(int.Parse(context.Subject.GetSubjectId()));
if (user != null)
{
var userTokenModel = _mapper.Map<UserTokenModel>(user);
var claims = new List<Claim>();
claims.Add(new Claim("UserId", user.UserId));
// Add another claims here
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
}
}
public class UserResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
UserRepository userRepository=new UserRepository();
var userLoginStatus = userRepository.GetUserById(context.UserName, context.Password);
if (userLoginStatus != null)
{
context.Result = new GrantValidationResult(userLoginStatus.UserId.ToString(),
OidcConstants.AuthenticationMethods.Password);
}
else
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidClient,
"Wrong Credentials");
}
}
}

Troubles authenticating native client to Azure AD securised MVC web app

I created both a MVC 5 web app hosted on Azure and a WPF client. My short term purpose (as if I can achieve that I'll be able to implement all my uses case) is the following:
Enforce Azure Ad authentification on the WPF client
Have the MVC web app to check through Azure Graph API the AD group membership of the user authentified in the client
Send back Graph API object to the client (IUser, Group...)
Use group membership to define Authorization on controllers
My actual issue is the following:
The user launch the app, and is prompted for authentication. I guess it work as I can display the user's mail and I have an access token.
The user tries to access a web api controller and it works fine
The user tries to access another web api controller decorated with [Authorize] and i get in return some HTML page stating this : "We can't sign you in.Your browser is currently set to block JavaScript. You need to allow JavaScript to use this service."
From what I've found searching on the web it seems that it could be related to my web app that is not configured properly (I already tried to add my webapp url in trusted sites and I'm sure that my controller URL is Ok). i cannot find much doc on native client + AAD + MVC so I don't really know how to correct it.
Here's my startup.auth.cs from the webapp :
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
private static string certName = ConfigurationManager.AppSettings["ida:CertName"];
public static readonly string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
string graphResourceId = ConfigurationManager.AppSettings["ida:GraphUrl"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
//
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
//
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
#region Certs (not used)
if (certName.Length != 0)
{
// Create a Client Credential Using a Certificate
//
// Initialize the Certificate Credential to be used by ADAL.
// First find the matching certificate in the cert store.
//
X509Certificate2 cert = null;
X509Store store = new X509Store(StoreLocation.CurrentUser);
try
{
store.Open(OpenFlags.ReadOnly);
// Place all certificates in an X509Certificate2Collection object.
X509Certificate2Collection certCollection = store.Certificates;
// Find unexpired certificates.
X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
// From the collection of unexpired certificates, find the ones with the correct name.
X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindBySubjectDistinguishedName, certName, false);
if (signingCert.Count == 0)
{
// No matching certificate found.
return Task.FromResult(0);
}
// Return the first certificate in the collection, has the right name and is current.
cert = signingCert[0];
}
finally
{
store.Close();
}
// Then create the certificate credential.
ClientAssertionCertificate credential = new ClientAssertionCertificate(clientId, cert);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(
"http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
AuthenticationHelper.token = result.AccessToken;
}
#endregion
else
{
// Create a Client Credential Using an Application Key
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
AuthenticationHelper.token = result.AccessToken;
}
return Task.FromResult(0);
}
}
});
}
}
Here's the controller which can be acceded when not decorated with [Authorize] but in that case the action throw a null exception (but if I can't get it fixed i'll post another question):
[System.Web.Http.Authorize]
public class UserADGraphController : ApiController
{
[ResponseType(typeof(IUser))]
[System.Web.Http.Route("api/UserADGraphController/GetMyInformations")]
public IHttpActionResult GetMyInformations()
{
try
{
string uID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
if (uID == null)
return Ok("UId null");
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
if (client == null)
return Ok("Client null");
IUser adUser = client.Users.Where(u => u.ObjectId == uID).ExecuteAsync().Result.CurrentPage.SingleOrDefault();
if (adUser == null)
{
return NotFound();
}
return Ok(adUser);
}
catch (Exception e)
{
return Ok(e.Message + " " + e.StackTrace);
}
and finally here are relevant parts of the client:
In the mainviewmodel class:
#region Azure AD auth properties
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
Uri redirectUri = new Uri(ConfigurationManager.AppSettings["ida:RedirectUri"]);
private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static string AppServiceResourceId = ConfigurationManager.AppSettings["todo:AppServiceResourceId"];
private static string AppServiceBaseAddress = ConfigurationManager.AppSettings["todo:AppServiceBaseAddress"];
private HttpClient httpClient;
private AuthenticationContext authContext = null;
#endregion
In the mainviewmodel constructor:
authContext = new AuthenticationContext(authority);
httpClient = new HttpClient();
My sign in method:
{
AuthenticationResult result = null;
try
{
result = authContext.AcquireToken(AppServiceResourceId, clientId, redirectUri, PromptBehavior.Auto);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
SignInLabelContent = "Connected to azure AD as " + result.UserInfo.DisplayableId;
}
catch (AdalException ex)
{
if (ex.ErrorCode == "user_interaction_required")
{
}
else
{
// An unexpected error occurred.
string message = ex.Message;
if (ex.InnerException != null)
{
message += "Inner Exception : " + ex.InnerException.Message;
}
Messenger.Default.Send<NotificationMessage>(new NotificationMessage(message));
//MessageBox.Show(message);
}
return;
}
}
The method that access the protected controller:
IUser me = null;
AuthenticationResult result = null;
result = authContext.AcquireToken(AppServiceResourceId, clientId, redirectUri, PromptBehavior.Auto);
string authHeader = result.CreateAuthorizationHeader();
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
//HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AppServiceBaseAddress + "/api/UserADGraphController/GetMyInformations");
//request.Headers.TryAddWithoutValidation("Authorization", authHeader);
//HttpResponseMessage response = await client.SendAsync(request);
//string responseString = await response.Content.ReadAsStringAsync();
//LogManager.log(responseString);
//Messenger.Default.Send<NotificationMessage>(new NotificationMessage(responseString));
HttpResponseMessage response = await httpClient.GetAsync(AppServiceBaseAddress + "/api/UserADGraphController/GetMyInformations");
if (response.IsSuccessStatusCode)
{
var jsonString = await response.Content.ReadAsStringAsync();
LogManager.log(jsonString);
me = JsonConvert.DeserializeObject<IUser>(jsonString);
//Messenger.Default.Send<NotificationMessage>(new NotificationMessage(jsonString));
}
In my case response has status code 200 but the jsonString contains the web page telling me about javascript disabled.
If someone has an idea it would be great !
Thanks !
If someone gets into this issue, I managed to solve it by changing my configureAuth method this way :
var azureADBearerAuthOptions = new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = tenant
};
azureADBearerAuthOptions.TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = audience
};
app.UseWindowsAzureActiveDirectoryBearerAuthentication(azureADBearerAuthOptions);
This error message is very misleading. I was getting the same problem, and found that my issue was actually mismatched Client Secret/AppURI settings.
From the error message, I assumed it was something I was doing in the ConfigureAuth method. Turns out I was mixing up dev and test settings.
Maybe this will help others who end up on this confusing error message.

Google OAuth 2 Error: redirect_uri_mismatch random url parameter ASP.NET

I've done authentication via VK, Instagram, Facebook in my site by template below.
However google requires "Redirect URL".
My redirect URL is like this:
http://localhost:4588/main/AuthenticationCallback?__provider__=google%2B&__sid__=6f3cc5957e4742758719f9b7decc2c09
Parameter "sid" is random every time. So I can't give google precise URL. I tried to input http://localhost:4588/main/AuthenticationCallback as I did for Instagram and it worked for Instagram but Google keeps showing me "400 Error: redirect_uri_mismatch"
I've also tried to pass http://localhost:4588/main/AuthenticationCallback as URL parameter in authorization url to google below. But in this case method "IAuthenticationClient.RequestAuthentication" is not called at all.
Can you advise me what should I input as "Redirect URL" for my Google app?
Template class working with OAuth2:
public class GoogleAuthenticationClient : IAuthenticationClient
{
public string appId;
public string appSecret;
private string redirectUri;
public GoogleAuthenticationClient(string appId, string appSecret)
{
this.appId = appId;
this.appSecret = appSecret;
}
string IAuthenticationClient.ProviderName
{
get { return "google+"; }
}
void IAuthenticationClient.RequestAuthentication(HttpContextBase context, Uri returnUrl)
{
var APP_ID = this.appId;
this.redirectUri = context.Server.UrlEncode(returnUrl.ToString());
var address = String.Format(
"https://accounts.google.com/o/oauth2/auth?client_id={0}&redirect_uri={1}&response_type=code&scope={2}",
APP_ID, this.redirectUri, "https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email"
);
HttpContext.Current.Response.Redirect(address, false);
}
class AccessToken
{
public string access_token = null;
public string user_id = null;
}
class UserData
{
public string uid = null;
public string first_name = null;
public string last_name = null;
public string photo_50 = null;
}
class UsersData
{
public UserData[] response = null;
}
AuthenticationResult IAuthenticationClient.VerifyAuthentication(HttpContextBase context)
{
try
{
string code = context.Request["code"];
var address = String.Format(
"https://accounts.google.com/o/oauth2/token?client_id={0}&client_secret={1}&code={2}&redirect_uri={3}",
this.appId, this.appSecret, code, this.redirectUri);
var response = GoogleAuthenticationClient.Load(address);
var accessToken = GoogleAuthenticationClient.DeserializeJson<AccessToken>(response);
address = String.Format(
"https://www.googleapis.com/plus/v1/people/{0}?access_token=1/fFBGRNJru1FQd44AzqT3Zg",
accessToken.user_id);
response = GoogleAuthenticationClient.Load(address);
var usersData = GoogleAuthenticationClient.DeserializeJson<UsersData>(response);
var userData = usersData.response.First();
return new AuthenticationResult(
true, (this as IAuthenticationClient).ProviderName, accessToken.user_id,
userData.first_name + " " + userData.last_name,
new Dictionary<string, string>());
}
catch (Exception ex)
{
return new AuthenticationResult(ex);
}
}
public static string Load(string address)
{
var request = WebRequest.Create(address) as HttpWebRequest;
using (var response = request.GetResponse() as HttpWebResponse)
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
return reader.ReadToEnd();
}
}
}
public static T DeserializeJson<T>(string input)
{
var serializer = new JavaScriptSerializer();
return serializer.Deserialize<T>(input);
}
}
Code in my Controller:
public void ExternalLogin(string provider)
{
OAuthWebSecurity.RegisterClient(
client: new GoogleAuthenticationClient(
"APP_ID", "APP_CODE"),
displayName: "google+", // надпись на кнопке
extraData: null);
ExternalLoginCallback(provider);
}
public void ExternalLoginCallback(string provider)
{
OAuthWebSecurity.RequestAuthentication(provider, Url.Action("AuthenticationCallback"));
}
public ActionResult AuthenticationCallback()
{
var result = OAuthWebSecurity.VerifyAuthentication();
if (result.IsSuccessful == false)
{
return null;
}
else
{
var provider = result.Provider;
var uniqueUserID = result.ProviderUserId;
return RedirectToAction("Main", "Main");
}
}
You can authorise a redirect URI as explained below, but you can't add any parameters to the redirect uri, please see this answer on how the parameters can be passed to Google google oauth2 redirect_uri with several parameters
The authorised redirect URI needs to be set when you created your client ("APP_ID", "APP_CODE") on the Google Cloud Console. Simply navigate to the API console for your project and edit the Web client to set the correct redirect URI you would like to use.

Resources