RapID QR code authentication
You can add a QR code login on your RapID enabled website. Your registered end-users scan the QR code with the app that you've provided and are securely logged in with two way authentication of their RapID credentials.
How it works
A random authentication code is generated and stored in your web server. Your website's login page renders that authentication code as a RapID QR code and listens for an authenticated event from the website. When your app scans the QR code, it reads the authentication code and POSTs that as JSON to an authentication URL on your website, along with the end-user's RapID credential. Your website uses the supplied credential to identify and validate the end-user, and marks the authentication code stored on your web server as authenticated. This causes the authenticated event to be fired and the end-user is redirected to your website's secure area.
How to implement it
You could of course develop this all for yourself, but to help you out, you can download our .NET Rapid.Authentication SDK which takes care of a lot of the work for you. You will need to integrate this SDK into your website and develop a QR code scanner feature in your mobile app.
The exact steps to implement this depend on the technologies used by your website and also the mobile operating system or systems that you support. The sections below show how to do this for some scenarios.
If the technologies you are using are not covered please contact rapid@intercede.com.
RapID QR code authentication in your website
Client certificate validation
If you are using the RapID library, then you must provide a class which implements the IValidateCertificate
interface
(namespace Rapid.Authentication.Authentication
). When your end-users scan the QR code and POST an authentication
request to your web server's URL, the RapID library will call the IsValidCertificate
method that you provide.
In some hosting environments it is not possible to deploy your RapID trusted issuer certificate in such a way that it would provide two way TLS validation of clients and thus prevent unauthorised access to your web server's routes. E.g. IIS manages trust check when it is running under full control of the host, but on shared systems such as Azure Web Apps, IIS is constrained and cannot do this.
If your hosting environment supports full two way validation then your IsValidCertificate
method can simply return true
.
For other hosting environments such as Azure Web Apps, you must provide code to explicitly validate client certificates.
Your method needs to validate that the certificate was issued by your RapID trusted issuer certificate.
The following example shows how you can validate certificates where the trusted issuer certificate is deployed to the file system of your web app.
using System;
using System.Security.Cryptography.X509Certificates;
using Rapid.Authentication.Authentication;
using Serilog;
public class CertificateValidator : IValidateCertificate
{
public bool IsValidClientCertificate(X509Certificate2 clientCertificate)
{
var chain = new X509Chain();
var trustedIssuerCertificate =
new X509Certificate2(
$@"{AppDomain.CurrentDomain.BaseDirectory
}\bin\RapID\Rapid_Your_Company_Name_TrustedCa.cer",
string.Empty);
Log.Logger.Information($"trustedIssuerCertificate = {trustedIssuerCertificate}");
chain.ChainPolicy.ExtraStore.Add(trustedIssuerCertificate);
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
var x509ChainBuiltOkay = chain.Build(clientCertificate);
Log.Logger.Information($"x509ChainBuiltOkay={x509ChainBuiltOkay}");
Log.Logger.Information($"chain.ChainElements.Count={chain.ChainElements.Count}");
var isValid =
x509ChainBuiltOkay &&
chain.ChainElements.Count == 2 &&
chain.ChainElements[1].Certificate.Thumbprint == trustedIssuerCertificate.Thumbprint &&
chain.ChainElements[0].Certificate.Thumbprint == clientCertificate.Thumbprint;
return isValid;
}
}
Note: ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority
is required because the .cer
file is not in the Trusted Root Certification Authorities certificate store. It is important to not simply rely
on the status from X509Chain.Build
since that would return true
if the client certificate is valid, but not necessarily issued
by your trusted issuer certificate.
IdentityServer3
The Rapid.Authentication SDK can be integrated into an IdentityServer3 solution, meaning that as well as the standard username/password login form, you can have a RapID QR code which end-users scan.
The notes here assume that you already have an IdentityServer3 solution which is providing local login with username and password. As such, it is assumed that you already have a custom user service, though we do not assume that you have a custom view service.
There is a wealth of online documentation for IdentityServer3 as well as sample code. The following links are of particular relevance to what is discussed here.
Custom User Service documentation
Custom View Service documentation
GitHub IdentityServer3 samples
Step 1. Add a reference to the Rapid.Authentication assembly
The Rapid.Authentication SDK that you downloaded includes a NuGet package for the Rapid.Authentication assembly. Extract this locally and install it into your identity server solution from the Visual Studio package manager console.
PM> install-package C:\Temp\Rapid.Authentication.n.n.n.nupkg
Step 2. Create an Identity Server logon store
As described above, each time a client browses to your logon page, a random authentication code is generated and stored in
your web server. Such authentication (or challenge) codes are encapsulated by the AuthenticationRequest
class (namespace Rapid.Authentication
).
For IdentityServer3, we need something to uniquely identify each logon request; the redirect URL meets this need.
To support systems such as IdentityServer3 which have a two-phase logon process, we provide a specialised version of
the AuthenticationRequest
class: TwoPhaseAuthenticationRequest
(namespace Rapid.Authentication
) into which you can
store the redirect URL. The RapID Library will later set up the AnonymousUserId.
Your identity server needs to store a TwoPhaseAuthenticationRequest
object for each rendered logon page. When an end-user scans
the QR code and POSTs to the authentication URL, the RapID library will locate the TwoPhaseAuthenticationRequest
via
the TwoPhaseAuthenticationKey
(redirect URL) and set the AnonymousUserId
using the supplied end-user RapID credential.
To hook this up, your TwoPhaseAuthenticationRequest
object store must derive from FindAuthenticationRequest<TwoPhaseAuthenticationRequest>
and must implement the interface INewAuthenticationRequest<TwoPhaseAuthenticationRequest>
(namespace Rapid.Authenticate.Persistence
).
In most cases this store will need to be persisted so that it is sharable by multiple instances of your identity server. For example; to support
load-balancing; or to facilitate both two way and one way TLS routes if deploying as Azure web services. However, for clarity, here is an
example in-memory implementation.
using Rapid.Authentication;
using Rapid.Authentication.Persistence;
public class TwoPhaseAuthenticationRequestStore
: FindAuthenticationRequest<TwoPhaseAuthenticationRequest>
, INewAuthenticationRequest<TwoPhaseAuthenticationRequest>
{
protected readonly List<TwoPhaseAuthenticationRequest> Requests =
new List<TwoPhaseAuthenticationRequest>();
public void Add(TwoPhaseAuthenticationRequest request)
{
Requests.Add(request);
}
public bool HasAuthenticated(string twoPhaseKey)
{
return AuthenticatedUserId(twoPhaseKey) != null;
}
public string AuthenticatedUserId(string twoPhaseKey)
{
return FindByTwoPhaseKey(twoPhaseKey)?.AnonymousUserId;
}
public override TwoPhaseAuthenticationRequest FindByChallenge(string challenge)
{
return Requests.SingleOrDefault(x => x.Challenge == challenge);
}
private TwoPhaseAuthenticationRequest FindByTwoPhaseKey(string key)
{
return Requests.SingleOrDefault(l => l.TwoPhaseAuthenticationKey == key);
}
}
For further details about implementing a persistent store see Persistent storage.
Step 3. Set up a RapID aware user service
We are assuming that your identity server already has a custom user service. You will need to make some changes to support RapID QR authentication. For clarity we will again use an in-memory implementation here, though again it is highly likely that your solution will need to make use of a persistent user store that is shared by multiple instances of your identity server.
You need to make your custom user service be RapID-aware and then override the IsActiveAsync
,
and PreAuthenticateAsync
methods so that they handle RapID authentication.
The constructor for your User Service is likely to already take a collection of users. Additionally, it must take the
TwoPhaseAuthenticationRequestStore
which you implemented in Step 2.
public class InMemoryRapidUserService : InMemoryUserService
{
private readonly List<InMemoryUser> users;
private readonly TwoPhaseAuthenticationRequestStore rapid;
public InMemoryRapidUserService(
List<InMemoryUser> users,
TwoPhaseAuthenticationRequestStore rapid)
: base(users)
{
this.users = users;
this.rapid = rapid;
}
Override PreAuthenticateAsync
, which is called before the Login page is shown.
The SignInMessage.ReturnUrl
of the supplied context
is the unique identity for
this IdentityServer3 logon. This function should examine your authenticationRequestStore
to see if the particular return URL has authenticated. If it has, then it should return
an AuthenticateResult
containing the end-user's anonymous ID and username.
public override Task PreAuthenticateAsync(PreAuthenticationContext context)
{
if (rapid.HasAuthenticated(context.SignInMessage.ReturnUrl))
{
var rapidId = rapid.AuthenticatedUserId(context.SignInMessage.ReturnUrl);
context.AuthenticateResult = AuthenticateResultForUser(rapidId);
}
return Task.FromResult(0);
}
private AuthenticateResult AuthenticateResultForUser(PreAuthenticationContext context, string rapidId)
{
var user = FindAuthenticatedUser(rapidId);
if (user == null) return null;
return new AuthenticateResult(user.Subject, user.Username);;
}
private InMemoryUser FindAuthenticatedUser(string anonId)
{
return anonId == null ? null : users.SingleOrDefault(u => u.ProviderId == anonId);
}
Override IsActiveAsync
, which is called whenever identity server needs to determine if the user is
still considered valid or active. Set IsActive to true if the context contains a subject Id, which will
be the case when the user has logged in.
public override Task IsActiveAsync(IsActiveContext context)
{
var subjectId = context.Subject.GetSubjectId();
if (!string.IsNullOrEmpty(subjectId))
{
context.IsActive = true;
}
return Task.FromResult(0);
}
Note: In the above example, we have used the IdentityServer3 InMemoryUser
class and
made use of the ProviderId
property to hold the anonymous ID. So making use of the test certificate requests that were
created for you when you signed up to RapID we may have a collection of users as follows:
public static class Users
{
public static List<InMemoryUser> Get()
{
return new List<InMemoryUser>
{
new InMemoryUser
{
Username = "bob",
Password = "secret",
Subject = "Bob Smith",
ProviderId = "TestCertificateRequestValid1",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Bob"),
new Claim(Constants.ClaimTypes.FamilyName, "Smith"),
new Claim(Constants.ClaimTypes.Role, "Supervisor"),
new Claim(Constants.ClaimTypes.Role, "Worker")
}
},
new InMemoryUser
{
Username = "jim",
Password = "secretjim",
Subject = "Jim Jones",
ProviderId = "TestCertificateRequestValid2",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Jim"),
new Claim(Constants.ClaimTypes.FamilyName, "Jones"),
new Claim(Constants.ClaimTypes.Role, "Worker"),
}
}
};
}
}
Step 4. Add the RapID QR code to the login page
To add the QR code to the identity server login page, you need to create a RapID aware view service. The examples here are based on the IdentityServer3 sample DefaultViewService.sln.
A custom view service allows you to override the default login pages that IdentityServer3 provides. By using a partial view, we are able
to insert the HTML to render the RapID QR Code in the login page, and by deriving a custom view service from DefaultViewService
we are able to
supply custom data to that html via the view model.
You need to add a _login.html
file to the templates
folder of your identity server solution. Then, after the Local Login <div/>
, include
a new <div/>
for the RapID QR code image (model.custom.rapidUrls.qrImageUrl
) and scan event handler (challenge_event_url
which uses model.custom.rapidUrls.eventsUrl
).
...
<div class="row">
<div class="col-md-6 col-sm-6" ng-show="model.loginUrl">
...
</div>
<div class="col-md-6 col-sm-6" ng-show="model.loginUrl">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">RapID Login</h3>
</div>
<div class="panel-body">
<input type="hidden" id="challenge_event_url" value="{{model.custom.rapidUrls.eventsUrl}}"/>
<form name="form" method="post" action="{{model.loginUrl}}">
<anti-forgery-token token="model.antiForgery"></anti-forgery-token>
<fieldset>
<div class="qr-code">
<img id="logon_bitmap" title="Scan QR code to logon." src="{{model.custom.rapidUrls.qrImageUrl}}" alt="Logon barcode"/>
</div>
<!--<div>
{{model.custom.code}}
</div>-->
<p class="qr-description">
Login with your phone
</p>
</fieldset>
</form>
</div>
<ul class="list-unstyled">
<li ng-repeat="link in model.additionalLinks"><a ng-href="{{link.href}}">{{link.text}}</a></li>
</ul>
</div>
</div>
...
</div>
...
You also need to create a view service, derived form DefaultViewService
and override the Login
method to include custom
data in the model that is supplied to the html form shown above.
public class RapidViewService : DefaultViewService
{
private readonly TwoPhaseAuthenticationFactory rapid;
public RapidViewService(
DefaultViewServiceOptions custom,
IViewLoader viewLoader,
TwoPhaseAuthenticationFactory rapid)
: base(custom, viewLoader)
{
this.rapid = rapid;
}
public override Task<Stream> Login(LoginViewModel model, SignInMessage message)
{
var authenticationRequest = rapid.StartAuthenticationRequest();
authenticationRequest.TwoPhaseAuthenticationKey = message.ReturnUrl;
authenticationRequest.Authenticated += (sender, args) => challenge.AnonymousUserId = args.Value;
model.Custom = new
{
Code = authenticationRequest.Challenge,
RapidUrls = new LogonUrls("http://MyIdentityServerAddress/rapid", challenge),
};
return base.Login(model, message);
}
}
Note: This makes use of a TwoPhaseAuthenticationFactory
class derived from AuthenticationFactory<T>
where
T
is an AuthenticationRequest
- in this case a TwoPhaseAuthenticationRequest
:
using Persistence;
public class TwoPhaseAuthenticationFactory : AuthenticationFactory<TwoPhaseAuthenticationRequest>
{
public TwoPhaseAuthenticationFactory(
INewAuthenticationRequest<TwoPhaseAuthenticationRequest> store)
: base(store)
{
}
public TwoPhaseAuthenticationFactory(
RapidContext context,
INewAuthenticationRequest<TwoPhaseAuthenticationRequest> store)
: base(context, store)
{
}
}
Step 5. Configure RapID Authentication in your Identity Server
The final step is to modify the Configuration method of the IdentityServer3's OwinStartup class.
You will already have code to register your custom user service, though as that now has a TwoPhaseAuthenticationRequestStore
being passed to its
constructor, you will need to also register that class in the Startup.Configuration
method to enable its construction within
your identity server by Autofac.
Additionally though, you need to instantiate the TwoPhaseAuthenticationRequestStore
and register it with the RapID library and identity server.
Registration with identity server is achieved via the IdentityServerServiceFactory.Register
method (along with some other RapID related classes/interfaces)
and registration with the RapID library is achieved by setting its FindLogonProvider
property. :
Note: The private helper method DefaultViewServiceWithRapidJavascript
which is used to inject RapID event handling javascript code into the login page.
public void Configuration(IAppBuilder app)
{
.
.
var factory = new IdentityServerServiceFactory()
.
.
var authenticationStore = new TwoPhaseAuthenticationRequestStore();
// Make the RapID library aware of the authentication request store
app.UseRapidQr("/rapid", new RapidQrOptions
{
FindAuthenticationRequestProvider = authenticationStore,
});
// Configure IdentityServer to use RapID
factory.UserService = new Registration<IUserService, InMemoryRapidUserService>();
factory.ViewService = new DefaultViewServiceRegistration<RapidViewService>(DefaultViewServiceWithRapidJavascript());
factory.Register(new Registration<TwoPhaseAuthenticationFactory>());
factory.Register(new Registration<INewAuthenticationRequest<TwoPhaseAuthenticationRequest>>(authenticationStore));
factory.Register(new Registration<TwoPhaseAuthenticationRequestStore>(authenticationStore));
.
.
.
app.UseIdentityServer(identityServerOptions);
}
private DefaultViewServiceOptions DefaultViewServiceWithRapidJavascript()
{
var options = new DefaultViewServiceOptions();
options.Scripts.Add("/rapid/Content/jquery.sse.js");
options.Scripts.Add("/rapid/Content/eventsource.js");
options.Scripts.Add("/rapid/Content/RapidQrEvents.js");
options.Scripts.Add("/rapid/Content/rapidAutoReload.js");
return options;
}
Now, your identity server's login page will include a QR code and when scanned, your end-users will be logged in and ready to go through whatever authorisation process you require.
Nancy
This section describes the process of integrating RapID QR authentication into an OWIN based Nancy website.
The steps outlined below will yield a simple website with RapID QR login. The website will make use of the Rapid.Authentication SDK to display the QR code which your users will be able to scan and be authenticated via the RapID credential that is already stored securely on their mobile device.
Steps 1 to 4 will create a minimal web server, based on the Katana - ASP.NET Host sample in the NancyFx GitHub wiki here. Subsequent steps will add RapID QR authentication to that website.
Step 1. Create an ASP.NET web app
Select an Empty ASP.NET project without folders and core references for Web Forms, MVC or Web API.
Install NuGet packages for Nancy and Owin:
PM> install-package Nancy
PM> install-package Microsoft.Owin.Host.SystemWeb
PM> install-package Microsoft.Owin.StaticFiles
PM> install-package Nancy.Owin
Step 2. Set up OWIN to use Nancy
Add the following to web.config
:
<appSettings>
<add key="owin:HandleAllRequests" value="true"/>
</appSettings>
And create an OWIN startup class:
using Owin;
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseNancy();
}
}
Step 3. Create a home page Nancy module
Your website will need a page that users can reach without being authenticated. Later, we will add the RapID QR code to this page, but for now we will keep it really simple.
using Nancy;
public class HomeModule : NancyModule
{
public HomeModule()
{
Get["/"] = x => "Hello";
}
}
Step 4. Create a secure page
Your website will also have pages that only authenticated users can reach. The
Nancy authentication overview has details of
how this works. In essence though, you simply create a NancyModule that uses RequiresAuthentication
to ensure that the route
is not available unless the user has authenticated.
using Nancy;
public class SecureModule : NancyModule
{
public SecureModule() : base ("secure")
{
this.RequiresAuthentication();
Get["/"] = x => $"Welcome {Context.CurrentUser.UserName}!";
}
}
Note: If you run your website at this stage and browse to the "/secure" route, you will not see the welcome message because you are not authenticated.
Step 5. Add a reference to the Rapid.Authentication assembly
The Rapid.Authentication SDK that you downloaded includes a NuGet package for the Rapid.Authentication assembly. Extract this locally and install it into your Nancy OWIN solution from the Visual Studio package manager console.
PM> install-package C:\Temp\Rapid.Authentication.n.n.n.nupkg
Step 6. Set up an authentication request store
The RapID QR code represents a randomly generated authentication (or challenge) code which is
encapsulated by the AuthenticationRequest
class (namespace Rapid.Authentication
). Your Nancy website
needs to store an AuthenticationRequest
object for each rendered logon page. When an end-user
scans the QR code and POSTs to the authentication URL, the RapID library locates the AuthenticationRequest
and
fires the Authenticated
event passing the AnonymousUserId
from the end-user's RapID credential.
The Rapid.Authentication SDK includes an in-memory AuthenticationRequest
store:
InMemoryAuthenticationRequestStore
(namespace Rapid.Authentication.Persistence
). This will work for
single server websites, but you must write your own persistent store for multiple server websites
(e.g. server farms, load balanced, etc.). Such persistent AuthenticationRequest
object stores must derive
from FindAuthenticationRequest<AuthenticationRequest>
and must implement the interface
INewAuthenticationRequest<AuthenticationRequest>
(namespace Rapid.Authenticate.Persistence
).
Step 7. Set up Nancy to use RapID QR authentication
Your Nancy website needs to be configured for RapID. This can be done through a custom bootstrapper class, derived from
DefaultNancyBootstrapper
. Any other customisation of your Nancy website will be placed here too.
using INewBasicLogonRequestStore = Rapid.Authentication.Persistence.INewAuthenticationRequest<Rapid.Authentication.AuthenticationRequest>;
public class CustomerNancyBootstrapper : DefaultNancyBootstrapper
{
private readonly INewBasicLogonRequestStore rapidAuthenticationStore;
public CustomerNancyBootstrapper(INewBasicLogonRequestStore authenticationRequestStore)
{
rapidAuthenticationStore = authenticationRequestStore;
}
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
RapidSessionAuthentication.Enable(pipelines);
}
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
base.ConfigureApplicationContainer(container);
container.Register(rapidAuthenticationStore);
}
}
With this CustomNancyBootstrapper
class in place, you can add code to your OWIN Startup
class's Configuration
method to hook up the AuthenticationRequest
store:
// This must be shared between your website and the RapID library
var inMemoryAuthenticationRequestStore = InMemoryAuthenticationRequestStore.NewInstance();
// Configure the RapID library
app.UseRapidQr("/rapid", new RapidQrOptions
{
FindAuthenticationRequestProvider = inMemoryAuthenticationRequestStore,
});
// Configure your website
app.UseNancy(options =>
{
options.Bootstrapper = new CustomerNancyBootstrapper(inMemoryAuthenticationRequestStore);
});
app.UseStageMarker(PipelineStage.MapHandler);
You are now ready to inject the required RapID authentication objects into your Nancy Module's constructor. You need an
AuthenticationFactory<AuthenticationRequest>
and a RapidNancyContext
:
using Rapid.Authentication;
using Rapid.Authentication.Website.Nancy;
public class HomeModule : NancyModule
{
public HomeModule(
AuthenticationFactory<AuthenticationRequest> rapidLogons,
RapidNancyContext context)
{
...
}
}
You need to add a RapID QR code to the HomeModule page that you created above. To achieve this using Nancy's Super Simple View Engine you will need a view model class:
using System;
using Nancy;
using Rapid.Authentication;
public class HomeVm
{
public string Code { get; set; }
public Uri QrCodeUrl;
public Url EventStreamUrl;
public LogonUrls Urls { get; set; }
}
and the HTML to render the QR code will access this view model. Using Master pages and sections this can be achieved as shown here:
Home.html:
@Master['master']
@Section['Title'] RapID QR logon in OWIN based Nancy website. @EndSection
@Section['PageSpacing']col-lg-12@EndSection
@Section['Content']
<div class="col-sm-6 col-md-6 col-lg-6">
@Partial['logonEvents.html'];
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
@Partial['logonCode.html']
</div>
@EndSection
@Section['OtherScript']
<script src="~/rapid/Content/eventsource.js"></script>
<script src="~/rapid/Content/jquery.sse.js"></script>
<script src="~/rapid/Content/RapidQrEvents.js"></script>
<script>
listenForRapidEvents("@Model.Urls.EventsUrl", function () {
window.location.href = "/secure";
});
</script>
@EndSection
Note: The script above will listen for RapID events which will cause the redirect to the SecureModule that you set up above when the QR code is scanned and the end-user is authenticated.
master.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>@Section['Title']</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
<link href="~/Content/rapid.css" rel="stylesheet">
<link href="~/Content/ie10-viewport-bug-workaround.css" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
@Section['Head']
</head>
<body>
<header>
<img class="banner" src="~/Content/images/heading.jpg" />
</header>
<section class="content-wrapper">
<div class="container">
<div class="row">
@Section['Content']
</div>
<div class="row">
@If.ErrorMessage
<div class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<span class="sr-only">Error:</span>
@Model.DisplayErrorMessage
</div>
@EndIf
</div>
</div>
</section>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
@Section['OtherScript']
</body>
</html>
logonEvents.html:
<div class="form-box">
<div class="form-content form-content-login">
<div>
<h3>Logon Event Stream</h3>
</div>
<div class="logon-events-connected">
<strong>Connection</strong>
<p style="display:block">Not connected</p>
</div>
<div class="logon-events-messages">
<strong>Events</strong>
</div>
</div>
</div>
logonCode.html:
<div class="form-box">
<div class="form-content form-content-login">
<div class="qr-code">
<img id="logon_bitmap" title="Scan QR code to logon." src="@Model.Urls.QrImageUrl" alt="Logon barcode"/>
</div>
<div>
@Model.Code
</div>
<p class="qr-description">
Logon with your phone, or <a href="~/signup">create an account</a>
</p>
</div>
</div>
With this html set up, the route handler in the HomeModule can be set up to handle RapID authentication:
public HomeModule(
AuthenticationFactory<AuthenticationRequest> rapidLogons,
RapidNancyContext context)
{
Get["/"] = x => HomeRoute(rapidLogons, rapidNancyContext);
}
public dynamic HomeRoute(
AuthenticationFactory<AuthenticationRequest> rapidLogons,
RapidNancyContext rapidNancyContext)
{
var challenge = rapidLogons.StartAuthenticationRequest();
challenge.Authenticated += (sender, args) => rapidNancyContext.OnAuthenticated(args.Value);
var viewModel = new HomeVm
{
Urls = new LogonUrls(rapidNancyContext.BaseUrl, challenge),
Code = challenge.Challenge,
};
var sessionCookie = new NancyCookie("RapidSessionId", challenge.SessionId, httpOnly: true, secure: false);
return View["Home", viewModel].WithCookie(sessionCookie);
}
Add the following to web.config
so that the required embedded assets will be loaded by IIS:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
If you run the website now, you should see a RapID QR code and if you scan it (e.g. using the RapID Logon test tool) you will be redirected to the secure page where you will see the anonymous ID from the client certificate that was supplied with the POST to the redirect URL.
QR Code Scanning
QR Code Scanning in iOS
Native iOS QR Code reader
The demo application bundled in the iOS Rapid SDK has a sample QR Code controller (QRCodeScannerViewController) that you can pull in to your own application and make minor modifications to suit your needs.
Summary of implementation
The native iOS QR Code scanner requires AVCaptureSession which is found in the AVFoundation framework. Make your controller
an AVCaptureMetadataOutputObjectsDelegate
to capture QR Code output and initialise your local AVCaptureSession
as follows.
#import <AVFoundation/AVFoundation.h>
@interface QRCodeScannerViewController () <AVCaptureMetadataOutputObjectsDelegate>
//Attach the viewPreview below to some view in the your storyboard.
@property (weak, nonatomic) IBOutlet UIView *viewPreview;
@property (nonatomic, strong) AVCaptureSession *captureSession;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewLayer;
@end
@implementation QRCodeScannerViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.captureSession = nil;
}
Create a method to start the QR Code scanner and call this in viewDidAppear
-(BOOL)startReading
{
NSError *error;
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
if (!input) {
NSLog(@"%@", [error localizedDescription]);
return NO;
}
self.captureSession = [[AVCaptureSession alloc] init];
[self.captureSession addInput:input];
AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
[self.captureSession addOutput:captureMetadataOutput];
dispatch_queue_t dispatchQueue;
dispatchQueue = dispatch_queue_create("myQueue", NULL);
[captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];
[captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
self.videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
[self.videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[self.videoPreviewLayer setFrame:self.viewPreview.layer.bounds];
[self.viewPreview.layer addSublayer:self.videoPreviewLayer];
[self.captureSession startRunning];
return YES;
}
Implement the delegate to capture output
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
if (metadataObjects != nil && [metadataObjects count] > 0)
{
AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];
if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode])
{
dispatch_async(dispatch_get_main_queue(), ^{
self.qrCodeContent = [metadataObj stringValue];
[_captureSession stopRunning];
_captureSession = nil;
[_videoPreviewLayer removeFromSuperlayer];
[self authenticate]; //use the QR code to authenticate
});
}
}
}
QR Code Scanning in Android
Native Android QR Code reader
There is no built in QR Code scanner in Android. The most popular solution is to use the zxing
QR Code apk.
Embedding ZXing QR Code scanner inside App
The source can be found on GitHub at the zxing project
You may find the following blog posts useful.
http://ribinsandroidhelper.blogspot.co.uk/2013/03/qr-code-reading-on-your-application.html
http://stackoverflow.com/questions/4854442/embed-zxing-library-without-using-barcode-scanner-app
Quick prototype solution using QR Code App installed on user device
The alternative is to use a QR Code scanner installed on the user's device. This can be a quick mechanism to help you prototype.
Start an intent to show QR Code scanner (e.g. when a button is clicked).
Intent intent = new Intent(
"com.google.zxing.client.android.SCAN");
intent.putExtra("SCAN_MODE", "QR_CODE_MODE");
startActivityForResult(intent, 0);
Override the onActivityResultMethod to get the QR Code
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == 0) {
if (resultCode == RESULT_OK) {
contents = intent.getStringExtra("SCAN_RESULT"); // This will contain your scan result
String format = intent.getStringExtra("SCAN_RESULT_FORMAT");
}
}