web api authentication from client side - asp.net-mvc

i have a www.api.com and a www.client.com
all registration will be done at api.com and login will be done at api.com. client.com will only be able to see the UI of the login form.
after user login and api.com return a token to user. How to i use the token to access the rest of the webapi in the api.com? i want to access the GetExployeeByID method. after use login. i stored the token in the sessionStorage.setItem('token', data.access_token)
api method
[RoutePrefix("api/Customer")]
public class CustomerController : ApiController
{
List<customer> list = new List<customer>() { new customer {id=1 ,customerName="Marry",age=13},
new customer { id = 2, customerName = "John", age = 24 } };
[Route("GetExployeeByID/{id:long}")]
[HttpGet]
[Authorize]
public customer GetExployeeByID(long id)
{
return list.FirstOrDefault(x=>x.id==id);
}
}
update 1
this is my ajax post to call the api after login
function lgetemp() {
$.ajax({
url: 'http://www.azapi.com:81/api/customer/GetExployeeByID/1',
datatype:"json",
type: 'get',
headers: {
"access_token":sessionStorage.getItem("token")
},
crossDomain: true,
success: function (data) {
debugger
alert(data.customerName)
},
error: function (err) {
debugger
alert('error')
}
})
}

You should pass the token in the header of the request from the client to the api
Authorization Basic yJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY=
The from your API you can query the headers and pull out the token.
string authorizationHeader = HttpContext.Current.Request.Headers["Authorization"];
string toke = authorizationHeader.Replace("Bearer ", String.Empty);
What I've done on my latest project is have a class AuthToken that does a lot of this for me
public class AuthToken : IAuthToken
{
private string _raw;
private IDictionary<string, string> _deserialized;
public string Raw
{
get
{
if (String.IsNullOrWhiteSpace(_raw))
{
string authorizationHeader = HttpContext.Current.Request.Headers["Authorization"];
_raw = authorizationHeader.Replace("Bearer ", String.Empty);
}
return _raw;
}
}
public IDictionary<string, string> Deserialized
{
get
{
if (_deserialized == null)
{
string[] tokenSplit = Raw.Split('.');
string payload = tokenSplit[1];
byte[] payloadBytes = Convert.FromBase64String(payload);
string payloadDecoded = Encoding.UTF8.GetString(payloadBytes);
_deserialized = JsonConvert.DeserializeObject<IDictionary<string, string>>(payloadDecoded);
}
return _deserialized;
}
}
}
Then I inject that into a UserContext class that I can inject into my controllers etc. The user context can then pull out claims from the token as needed. (assuming its a JWT)
public class UserContext : IUserContext
{
private IList<Claim> _claims;
private string _identifier;
private string _email;
private string _clientId;
public IAuthToken Token { get; }
public IList<Claim> Claims
{
get
{
return _claims ?? (_claims = Token.Deserialized.Select(self => new Claim(self.Key, self.Value)).ToList());
}
}
public string Identifier => _identifier ?? (_identifier = Token.Deserialized.ContainsKey("sub") ? Token.Deserialized["sub"] : null);
public string Email => _email ?? (_email = Token.Deserialized.ContainsKey(ClaimTypes.Email) ? Token.Deserialized[ClaimTypes.Email] : null);
public UserContext(IAuthToken authToken)
{
Token = authToken;
}
}

You need to pass the token to the request header and make the call to the API url. Below function can be called by passing the URL and token which you have.
static string CallApi(string url, string token)
{
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new HttpClient())
{
if (!string.IsNullOrWhiteSpace(token))
{
var t = JsonConvert.DeserializeObject<Token>(token);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + t.access_token);
}
var response = client.GetAsync(url).Result;
return response.Content.ReadAsStringAsync().Result;
}
}
Refer- Token based authentication in Web API for a detailed explanation.

Related

Getting Error 400: invalid_request while redirecting Authorization page

Following this guide (https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-asp.net-mvc) I made the code work in localhost but it gives an Error 400: invalid_request device_id and device_name are required for private IP: http://xx.xx.xx.xx:xxxxx/AuthCallback/IndexAsync while redirecting to Authorization page. We added subdomain in dns for our windows server ip address and registered the same subdomain in google api console. Why google gets ip address of the server instead of subdomain? Is the problem related to dns?
These are the working codes on local machine:
HomeController.cs
public class HomeController : Controller
{
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var service = new GmailService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "BPM Mail"
});
var gmailProfile = service.Users.GetProfile("me").Execute();
var userGmailEmail = gmailProfile.EmailAddress;
Utils.userMail = userGmailEmail;
Utils.cred = result.Credential;
// SAMPLE CODE:
//var list = await service.Files.List().ExecuteAsync();
//ViewBag.Message = "FILE COUNT IS: " + list.Items.Count();
return new RedirectResult("~/Apps/Mail/Default.aspx");
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
}
AppFlowMetadata.cs
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "ClientId ",
ClientSecret = "ClientSecret "
},
Scopes = new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.MailGoogleCom, GmailService.Scope.GmailModify },
DataStore = new FileDataStore("D:/bpm_mail/mytokens/token.json", true)
});
public override string GetUserId(Controller controller)
{
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
var user = controller.Session["user"];
//Utils.userId = user.ToString();
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
//public override string AuthCallback
//{
// get { return #"/AuthCallback/IndexAsync"; }
//}
}
AuthCallbackController.cs
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
Issue solved by changing private ip to public one and redirect it to domain.

JWT Authentication ASP.NET Core MVC application

I've seen numerous examples of how to use JWT authentication with Angular, React, Vue etc... clients but can't find any examples of using JWT authentication with ASP.NET Core (specifically 2.2) Web App Mvc.
Does anyone have any examples or advice on how to do this?
Thanks,
You can use this class based on nuget package JWT 3.0.3
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Newtonsoft.Json;
using System;
namespace Common.Utils
{
public class JwtToken
{
private IJwtEncoder encoder;
private IJwtDecoder decoder;
/// <remarks>
/// This requires a key value randomly generated and stored in your configuration settings.
/// Consider that it is a good practice use keys as at least long as the output digest bytes
/// length produced by the hashing algorithm used. Since we use an HMAC-SHA-512 algorithm,
/// then we can provide it a key at least 64 bytes long.
/// <see cref="https://tools.ietf.org/html/rfc4868#page-7"/>
/// </remarks>
public string SecretKey { get; set; }
public JwtToken()
{
IJwtAlgorithm algorithm = new HMACSHA512Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider datetimeProvider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, datetimeProvider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
decoder = new JwtDecoder(serializer, validator, urlEncoder);
SecretKey = "";
}
public JwtToken(string secretKey) : this()
{
SecretKey = secretKey;
}
public bool IsTokenValid(string token)
{
return !string.IsNullOrWhiteSpace(DecodeToken(token));
}
public string GetToken(object payload)
{
try
{
return encoder.Encode(payload, SecretKey);
}
catch (Exception)
{
return encoder.Encode(new DataModel(payload), SecretKey);
}
}
public string DecodeToken(string token)
{
try
{
if (string.IsNullOrWhiteSpace(token) || token == "null")
{
return null;
}
return decoder.Decode(token, SecretKey, true);
}
catch (TokenExpiredException)
{
return null;
}
catch (SignatureVerificationException)
{
return null;
}
}
public T DecodeToken<T>(string token) where T : class
{
try
{
if (string.IsNullOrWhiteSpace(token))
{
return null;
}
return decoder.DecodeToObject<T>(token, SecretKey, true);
}
catch (TokenExpiredException)
{
return null;
}
catch (SignatureVerificationException)
{
return null;
}
catch (Exception)
{
var data = decoder.DecodeToObject<DataModel>(token, SecretKey, true).Data;
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(data));
}
}
}
public class DataModel
{
public DataModel(object data)
{
Data = data;
}
public object Data { get; set; }
}
}
Then in your Startup class Configure method set the jwt middleware
for check authentication status of each request:
app.Use((context, next) =>
{
// verify app access token if not another service call
var appAccessToken = context.Request.Headers["Authorization"];
if (appAccessToken.Count == 0)
{
context.Items["User"] = null;
}
else
{
var token = appAccessToken.ToString().Replace("Bearer ", "");
var jwtToken = new JwtToken(config.JwtTokenSecret); //you need a secret (with requirements specified above) in your configuration (db, appsettings.json)
if (string.IsNullOrWhiteSpace(token) || !jwtToken.IsTokenValid(token))
{
context.Response.StatusCode = 401;
return Task.FromResult(0);
}
dynamic user = jwtToken.DecodeToken<dynamic>(token);
var cachedToken = cache.Get(user.Id); //you need some cache for store your token after login success and so can check against
if (cachedToken == null || cachedToken.ToString() != token)
{
context.Response.StatusCode = 401;
return Task.FromResult(0);
}
context.Items["User"] = new Dictionary<string, string>() {
{ "FullName",user.Name?.ToString()},
{ "FirstName",user.FirstName?.ToString()},
{ "LastName",user.LastName?.ToString()},
{ "Role",user.Role?.ToString()},
{ "Email",user.Email?.ToString()}
};
}
return next();
});
And finally you need generate the token and return it after
authentication:
[AllowAnonymous]
public IActionResult Login(string username, string password)
{
User user = null; //you need some User class with the structure of the previous dictionary
if (checkAuthenticationOK(username, password, out user)) //chackAuthenticationOk sets the user against db data after a succesfull authentication
{
var token = new JwtToken(_config.JwtTokenSecret).GetToken(user); //_config is an object to your configuration
_cache.Set(user.id, token); //store in the cache the token for checking in each request
return Ok(token);
}
return StatusCode(401, "User is not authorized");
}
Add following code to startup
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Issuer"],
ValidAudience = Configuration["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SigningKey"]))
};
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,, ILoggerFactory loggerFactory)
{
app.UseAuthentication();
}
Code for login action in AccountController
[Route("api/[controller]")]
public class AccountController : Controller
{
[AllowAnonymous]
[HttpPost]
[Route("login")]
public IActionResult Login([FromBody]LoginViewModel loginViewModel)
{
if (ModelState.IsValid)
{
var user = _userService.Authenticate(loginViewModel);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, loginViewModel.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken
(
issuer: _configuration["Issuer"],
audience: _configuration["Audience"],
claims: claims,
expires: DateTime.UtcNow.AddDays(10),
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])),
SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
access_token = new JwtSecurityTokenHandler().WriteToken(token),
expires_in = (int)token.ValidTo.Subtract(DateTime.UtcNow).TotalSeconds,// TimeSpan.FromTicks( token.ValidTo.Ticks).TotalSeconds,
sub = loginViewModel.Username,
name = loginViewModel.Username,
fullName = user.FullName,
jobtitle = string.Empty,
phone = string.Empty,
email = user.EmailName,
});
}
}
}
I assume you have implemented JWT on the server side. To handle this on client side, first you have to add token to web browser local storage. Add to your main layout javascript (let's named it AuthService.js)
below code adds token to local storage after login button clicked. gettokenfromlocalstorage() retrieve token from local storage.
<script>
var token = "";
function Loginclick() {
var form = document.querySelector('form');
var data = new FormData(form);
var authsevice = new AuthService();
authsevice.LogIn(data.get("username").toString(), data.get("password").toString());
}
function gettokenfromlocalstorage() {
var authserv = new AuthService();
var mytoken = authserv.getAuth();
authserv.LogOut();
}
var AuthService = /** #class */ (function () {
function AuthService() {
this.authKey = "auth";
}
AuthService.prototype.LogIn = function (username, password) {
this.username = username;
this.password = password;
this.grant_type = "password";
this.client_id = "MyClientId";
var loginurl = "/api/Token/Auth";
var xhr = new XMLHttpRequest();
xhr.open("POST", loginurl, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(this));
xhr.onreadystatechange = function () {
console.log("onreadystatechange");
};
xhr.onerror = function () {
var aaa = this.responseText;
};
xhr.onload = function () {
var data = JSON.parse(this.responseText);
var auth = new AuthService();
auth.setAuth(data);
};
};
AuthService.prototype.LogOut = function () {
this.setAuth(null);
return true;
};
AuthService.prototype.setAuth = function (auth) {
if (auth) {
localStorage.setItem(this.authKey, JSON.stringify(auth));
}
else {
localStorage.removeItem(this.authKey);
}
return true;
};
AuthService.prototype.getAuth = function () {
var i = localStorage.getItem(this.authKey);
return i === null ? null : JSON.parse(i);
};
AuthService.prototype.isLoggedIn = function () {
return localStorage.getItem(this.authKey) !== null ? true : false;
};
return AuthService;
}());
var aa = new AuthService();
var gettoken = aa.getAuth();
if (gettoken !== null) {
token = gettoken.token;
}
</script>
To add token to the header of each anchor tag put below script also to
your main layout.
<script>
var links = $('a');
for (var i = 0; i < links.length; i++) {
links[i].onclick = function check() {
addheader(this.href);
return false;
}
}
function addheader(object) {
let xhr = new XMLHttpRequest();
xhr.open("GET", object, true);
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.send(null);
xhr.onload = function () {
window.history.pushState("/", "", xhr.responseURL);
//mycontainer is a div for parialview content
$("#mycontainer").html(xhr.responseText);
window.onpopstate = function (e) {
if (e.state) {
$("html").html = e.state;
document.title = e.state.pageTitle;
}
};
};
}
</script>
Remember that using of this approach, each view has to be loaded as a partial view.
If you insert url address in a web browser bar directly this solution doesn't work. I haven't figured it out yet. That's why to manage token authentication is better using single page application, not multipage application.
You can use this boilerplate to understand how to implement JWT tokenization with .Net Core. In the project you can find JWT, Swagger and EF features.

Service to Service call and Graph APi

I registered a web API https://dev.office.com/app-registration. I kept sign URL
localhost:8000 and RedirectURI as localhost:8000.
After this, I created a console application to access the graph API. Here is the code. But it doesnt work. I get the error "access denied".
Am I missing anything here?
One more thing, I checked the access_token and I dont see any user information there.
string clientId = "<>";
string clientsecret = "<>";
string tenant = "my.com";
var authUri = "https://login.microsoftonline.com/" + tenant + "/oauth2/token";
var RESOURCE_URL = "https://graph.microsoft.com";
HttpClient client = new HttpClient();
var authContext = new AuthenticationContext(authUri);
var credential = new ClientCredential(clientId: clientId, clientSecret: clientsecret);
var result = authContext.AcquireTokenAsync(RESOURCE_URL, credential).Result;
Console.WriteLine(result.AccessToken.ToString());
client.DefaultRequestHeaders.Add("Authorization", "bearer " + result.AccessToken);
var httpContent = new StringContent(content, Encoding.GetEncoding("utf-8"), "application/json");
var response = client.GetAsync("https://graph.microsoft.com/v1.0/users/ankushb/calendar/events").Result;
// var response = client.PostAsync("https://graph.microsoft.com/v1.0/groups", httpContent).Result;
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
There are two basic authentication flow for OAuth2 that Azure AD use.
First is authorization code grant flow which used to access to web APIs by native clients and websites.In this flow, the user delegates access to a client application.
Second is Client Credentials grant flow which permits a web service (a confidential client) to use its own credentials to authenticate when calling another web service, instead of impersonating a user. In this scenario, the client is typically a middle-tier web service, a daemon service, or web site.
The code above is using the Client Credentials grant flow, that's the reason there is no user information.
In your scenario, you should register an native app(console application is not confidential clients which should not use the secret to request for the token ) and use the authorization code grant flow to enable users sign-in with their account. And to use this flow in a console application, we need to prompt a web dialog navigate to the login-in page and get the auth code to request for the access token.
Here is an sample to authenticate the app with authorization code grant flow:
var oauth = new OauthConfiguration
{
Authority = "https://login.microsoftonline.com",
Tenant = "common",
ClientId = "{clientId}",
RedirectURI = "{redirectURL}",
Secret = ""
};
var tokenResponse = new OAuth2.OauthWebAuthHelper(oauth).AcquireTokenWithResource("https://graph.microsoft.com");
var accessToken = tokenResponse.GetValue("access_token").Value<string>();
var refreshToken = tokenResponse.GetValue("refresh_token").Value<string>();
namespace OAuth2
{
public class OauthWebAuthHelper
{
public enum Version
{
V1 = 1,
V2 = 2
}
private OauthConfiguration _configuration;
private const string OAUTH2_AUTHORIZE_V1_SUFFIX = #"oauth2/";
private const string OAUTH2_AUTHORIZE_V2_SUFFIX = #"oauth2/v2.0";
private string _authorizeSuffix;
public OauthWebAuthHelper(OauthConfiguration configuration, Version version = Version.V1)
{
_configuration = configuration;
switch (version)
{
case Version.V1: _authorizeSuffix = OAUTH2_AUTHORIZE_V1_SUFFIX; break;
case Version.V2: _authorizeSuffix = OAUTH2_AUTHORIZE_V2_SUFFIX; break;
}
}
public void LogOut()
{
var dialog = new WebBrowserDialog();
dialog.Open(string.Format("{0}/logout", EndPointUrl));
}
protected string EndPointUrl
{
get
{
return string.Format("{0}/{1}/{2}", _configuration.Authority, _configuration.Tenant, _authorizeSuffix);
}
}
public JObject GetAuthorizationCode()
{
JObject response = new JObject();
var parameters = new Dictionary<string, string>
{
{ "response_type", "code" },
{ "client_id", _configuration.ClientId },
{ "redirect_uri", _configuration.RedirectURI },
{ "prompt", "login"}
};
var requestUrl = string.Format("{0}/authorize?{1}", EndPointUrl, BuildQueryString(parameters));
var dialog = new WebBrowserDialog();
dialog.OnNavigated((sender, arg) =>
{
if (arg.Url.AbsoluteUri.StartsWith(_configuration.RedirectURI))
{
var collection = HttpUtility.ParseQueryString(arg.Url.Query);
foreach (var key in collection.AllKeys)
{
response.Add(key, collection[key]);
}
dialog.Close();
}
});
dialog.Open(requestUrl);
return response;
}
public JObject GetAuthorizationCode(string scope)
{
JObject response = new JObject();
var parameters = new Dictionary<string, string>
{
{ "response_type", "code" },
{ "client_id", _configuration.ClientId },
{ "redirect_uri", _configuration.RedirectURI },
{ "prompt", "login"},
{ "scope", scope}
};
var requestUrl = string.Format("{0}/authorize?{1}", EndPointUrl, BuildQueryString(parameters));
var dialog = new WebBrowserDialog();
dialog.OnNavigated((sender, arg) =>
{
if (arg.Url.AbsoluteUri.StartsWith(_configuration.RedirectURI))
{
var collection = HttpUtility.ParseQueryString(arg.Url.Query);
foreach (var key in collection.AllKeys)
{
response.Add(key, collection[key]);
}
dialog.Close();
}
});
dialog.Open(requestUrl);
return response;
}
public JObject AcquireTokenWithResource(string resource)
{
var codeResponse = GetAuthorizationCode();
var code = codeResponse.GetValue("code").Value<string>();
var parameters = new Dictionary<string, string>
{
{ "resource", resource},
{ "client_id", _configuration.ClientId },
{ "code", code},
{ "grant_type", "authorization_code" },
{ "redirect_uri", _configuration.RedirectURI},
{ "client_secret",_configuration.Secret}
};
var client = new HttpClient();
var content = new StringContent(BuildQueryString(parameters), Encoding.GetEncoding("utf-8"), "application/x-www-form-urlencoded");
var url = string.Format("{0}/token", EndPointUrl);
var response = client.PostAsync(url, content).Result;
var text = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject(text) as JObject;
}
public JObject RefreshTokenWithResource(string refreshToken)
{
var parameters = new Dictionary<string, string>
{
{ "client_id", _configuration.ClientId },
{ "refresh_token", refreshToken},
{ "grant_type", "refresh_token" }
};
var client = new HttpClient();
var content = new StringContent(BuildQueryString(parameters), Encoding.GetEncoding("utf-8"), "application/x-www-form-urlencoded");
var url = string.Format("{0}/token", EndPointUrl);
var response = client.PostAsync(url, content).Result;
var text = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject(text) as JObject;
}
public JObject AcquireTokenWithScope(string scope)
{
var codeResponse = GetAuthorizationCode(scope);
var code = codeResponse.GetValue("code").Value<string>();
var parameters = new Dictionary<string, string>
{
{ "client_id", _configuration.ClientId },
{ "code", code},
{ "grant_type", "authorization_code" },
{ "redirect_uri", _configuration.RedirectURI},
};
var client = new HttpClient();
var content = new StringContent(BuildQueryString(parameters), Encoding.GetEncoding("utf-8"), "application/x-www-form-urlencoded");
var url = string.Format("{0}/token", EndPointUrl);
var response = client.PostAsync(url, content).Result;
var text = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject(text) as JObject;
}
private string BuildQueryString(IDictionary<string, string> parameters)
{
var list = new List<string>();
foreach (var parameter in parameters)
{
if (!string.IsNullOrEmpty(parameter.Value))
list.Add(string.Format("{0}={1}", parameter.Key, HttpUtility.UrlEncode(parameter.Value)));
}
return string.Join("&", list);
}
}
public class OauthConfiguration
{
public string Authority { get; set; }
public string Tenant { get; set; }
public string ClientId { get; set; }
public string RedirectURI { get; set; }
public string Secret { get; set; }
}
public class WebBrowserDialog
{
private const int DEFAULT_WIDTH = 400;
private const int DEFAULT_HEIGHT = 500;
private Form _displayLoginForm;
private string _title;
private WebBrowser _browser;
private WebBrowserNavigatedEventHandler _webBrowserNavigatedEventHandler;
public WebBrowserDialog()
{
_title = "OAuth Basic";
_browser = new WebBrowser();
_browser.Width = DEFAULT_WIDTH;
_browser.Height = DEFAULT_HEIGHT;
_browser.Navigated += WebBrowserNavigatedEventHandler;
_displayLoginForm = new Form();
_displayLoginForm.SuspendLayout();
_displayLoginForm.Width = DEFAULT_WIDTH;
_displayLoginForm.Height = DEFAULT_HEIGHT;
_displayLoginForm.Text = _title;
_displayLoginForm.Controls.Add(_browser);
_displayLoginForm.ResumeLayout(false);
}
public void OnNavigated(WebBrowserNavigatedEventHandler handler)
{
_webBrowserNavigatedEventHandler = handler;
}
protected void WebBrowserNavigatedEventHandler(object sender, WebBrowserNavigatedEventArgs e)
{
if(_webBrowserNavigatedEventHandler != null)
{
_webBrowserNavigatedEventHandler.Invoke(sender, e);
}
}
public void Open(string url)
{
_browser.Navigate(url);
_displayLoginForm.ShowDialog();
}
public void Close()
{
_displayLoginForm.Close();
}
}
}
And you can get more samples about Microsoft Graph from here.

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.

Why am I not receiving a RefreshToken from a Google OAuth request?

I am attempting to integrate Google Calendar into my application and I am having some problems with the OAuth authorization passing off a RefreshToken. I receive an AccessToken with no issue, but the RefreshToken property is null. See the line marked "ERROR HERE:" for where I am having the issue
My Asp.Net MVC controller (named OAuthController) looks like the following:
public ActionResult Index()
{
var client = CreateClient();
client.RequestUserAuthorization(new[] { "https://www.googleapis.com/auth/calendar" }, new Uri("http://localhost/FL.Evaluation.Web/OAuth/CallBack"));
return View();
}
public ActionResult CallBack()
{
if (string.IsNullOrEmpty(Request.QueryString["code"])) return null;
var client = CreateClient();
// Now getting a 400 Bad Request here
var state = client.ProcessUserAuthorization();
// ERROR HERE: The RefreshToken is NULL
HttpContext.Session["REFRESH_TOKEN"] = Convert.ToBase64String(Encoding.Unicode.GetBytes(state.RefreshToken));
return JavaScript("Completed!");
}
private static WebServerClient CreateClient()
{
return
new WebServerClient(
new AuthorizationServerDescription()
{
TokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token"),
AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
ProtocolVersion = ProtocolVersion.V20
}
, _GoogleClientId, _GoogleSecret);
}
I see in Google's API documents, that I need to ensure that the access_type requested is set to offline for a RefreshToken to be sent. How do I set this value in my Authenticator request?
After hours of fiddling with DotNetOpenAuth and the Google APIs published for .Net, I got nowhere fast. I decided to circumvent the libraries and went directly at the Google REST API with native HttpRequest and HttpResponse objects. My sanitized code for my MVC controller follows:
private static string _GoogleClientId = "CLIENT_ID";
private static string _GoogleSecret = "SECRET";
private static string _ReturnUrl = "http://localhost/OAuth/CallBack";
public ActionResult Index()
{
return Redirect(GenerateGoogleOAuthUrl());
}
private string GenerateGoogleOAuthUrl()
{
//NOTE: Key piece here, from Andrew's reply -> access_type=offline forces a refresh token to be issued
string Url = "https://accounts.google.com/o/oauth2/auth?scope={0}&redirect_uri={1}&response_type={2}&client_id={3}&state={4}&access_type=offline&approval_prompt=force";
string scope = UrlEncodeForGoogle("https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.readonly").Replace("%20", "+");
string redirect_uri_encode = UrlEncodeForGoogle(_ReturnUrl);
string response_type = "code";
string state = "";
return string.Format(Url, scope, redirect_uri_encode, response_type, _GoogleClientId, state);
}
private static string UrlEncodeForGoogle(string url)
{
string UnReservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
var result = new StringBuilder();
foreach (char symbol in url)
{
if (UnReservedChars.IndexOf(symbol) != -1)
{
result.Append(symbol);
}
else
{
result.Append('%' + String.Format("{0:X2}", (int)symbol));
}
}
return result.ToString();
}
class GoogleTokenData
{
public string Access_Token { get; set; }
public string Refresh_Token { get; set; }
public string Expires_In { get; set; }
public string Token_Type { get; set; }
}
public ActionResult CallBack(string code, bool? remove)
{
if (remove.HasValue && remove.Value)
{
Session["GoogleAPIToken"] = null;
return HttpNotFound();
}
if (string.IsNullOrEmpty(code)) return Content("Missing code");
string Url = "https://accounts.google.com/o/oauth2/token";
string grant_type = "authorization_code";
string redirect_uri_encode = UrlEncodeForGoogle(_ReturnUrl);
string data = "code={0}&client_id={1}&client_secret={2}&redirect_uri={3}&grant_type={4}";
HttpWebRequest request = HttpWebRequest.Create(Url) as HttpWebRequest;
string result = null;
request.Method = "POST";
request.KeepAlive = true;
request.ContentType = "application/x-www-form-urlencoded";
string param = string.Format(data, code, _GoogleClientId, _GoogleSecret, redirect_uri_encode, grant_type);
var bs = Encoding.UTF8.GetBytes(param);
using (Stream reqStream = request.GetRequestStream())
{
reqStream.Write(bs, 0, bs.Length);
}
using (WebResponse response = request.GetResponse())
{
var sr = new StreamReader(response.GetResponseStream());
result = sr.ReadToEnd();
sr.Close();
}
var jsonSerializer = new JavaScriptSerializer();
var tokenData = jsonSerializer.Deserialize<GoogleTokenData>(result);
Session["GoogleAPIToken"] = tokenData.Access_Token;
return JavaScript("Refresh Token: " + tokenData.Refresh_Token);
}
Big thanks to Kelp for a bit of the code in this snippet.
Adjust GoogleAuthenticationServer.Description to have an authorization endpoint URI that includes ?access_type=offline in the query string.
Simply add
AccessType = "offline",
to GoogleOAuth2AuthenticationOptions() object.

Resources